interface
表示的是接口类型,相当于定义一个对象有哪些属性;
?:
表示可选属性;
[k: ]: any;
表示其他未知属性;
interface Person {
name: string;
age:number;
fn?: (...args: any[]) => void;
[k: string]: any;
}
const person: Person = {
name: '张三',
age: 10,
}
接口可以extends
继承自另一个interface
接口 或者是接口类型的type
;
interface Name {
name: string;
}
interface Person extends Name {
age: number;
}
接口可以多次重复定义,最终合并效果同上
interface Person {
name: string;
}
interface Person {
age: number;
}
下面这种写法为什么会报错?
interface Props {
[key: string]: string;
}
interface dataType {
title: string;
}
const data: dataType = {
title: '',
};
const props: Props = data;
//不能将类型“dataType”分配给类型“Props”。
// 类型“dataType”中缺少类型“string”的索引签名。
看似dataType
是 Props
的子集,实际dataType
可以多次定义
interface Props {
[key: string]: string;
}
interface dataType {
name: string;
}
const data: dataType = {
name: 'zzz',
};
const props: Props = data;
interface dataType {
age: number;
}
这样一看就知道是不能把dataType
赋值给 Props
的,这种时候就要用type
去定义。
type
定义一个类型,可以是接口类型,也可以是基本类型包括:number
,string
,null
,underfined
,boolean
…
或者是模板类型'hello'
,`hello ${sting}`
等等;
type Person = {
name: string;
age:number;
fn?: (...args: any[]) => void;
[k: string]: any;
}
type N = number;
type S = 'a' | 'b';
type P = Person;
type T = `hello ${N}`;
...
泛型表示通过传入的类型确定最终的类型,用于支持多个类型数据;
function identity<T>(arg: T): T {
return arg;
}
当你不想通过any
丢失信息,同时又具体用到了某些属性时,
可以通过extends
做泛型约束
;
function arean<T extends { width: number; height: number }>(arg: T): number {
return arg.width * arg.height;
}
T & R
表示 既是 T
又是 R
;
interface Name {
name: string;
}
interface Age {
age: number;
}
type Person = Name & Age;
const personOne: Person = {
name: '张三',
age: 22,
}
&
的作用是合并两个类型,一般情况下与接口extends
作用差不多,
但是&
不会做定义类型时的前置检查,例如:
type A = {
a: string;
c: number;
};
type B = {
b: string;
c: string;
};
type C = A & B;
//不能将类型“string”分配给类型“never”。
const c1: C = {
a: '',
b: '',
c: '',
};
//不能将类型“number”分配给类型“never”。
const c2: C = {
a: '',
b: '',
c: 1,
};
上述例子应A
和B
类型合并时c
属性是即是number
又是string
,最终的结果:
type C = {
a: string;
b: string;
c: never;
};
也有可能结果为never
;取决于never
的位置;
这种object
类型的&
生成的结果更像是并集
;但是并不能把&
看成并集
type A = 'a'|'c'|'d';
type B = 'b'|'c'|'d';
type C = A & B; // ’c'|'d'
上面这个例子就是一个交集
的效果;
无论赋什么值都会报错。
而当我们用extends
时,会在定义时直接报错提醒:
type A = {
a: string;
c: number;
};
//不能将类型“string”分配给类型“number”。
interface C extends A {
b: string;
c: string;
}
如果一个值是交叉类型,(有可能获取的是交集
也可能是并集
)。
联合类型表示一个值可以是几种类型之一。 我们用竖线(|
)分隔每个类型,所以 number | string | boolean
表示一个值可以是 number
, string
,或 boolean
。
如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有
的成员。
官方提供的常用类型工具:
// 使所有T的属性可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 使所有T的属性必选
type Required<T> = {
[P in keyof T]-?: T[P];
};
// 使所有T的属性只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 从T中挑选在联合类型K的中属性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// 构造一个key在K中,值为T的类型
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// 从T类型中排除U类型
type Exclude<T, U> = T extends U ? never : T;
// 从T类型中提取U共有类型
type Extract<T, U> = T extends U ? T : never;
// 从T中忽略K中的key
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 从T中忽略null和underfined
type NonNullable<T> = T & {};
// 推断一个函数参数的类型
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// 推断一个构造函数参数的类型
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
// 推断一个函数返回值的类型
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
当使用联合类型或其他时,我们时常会遇到这种情况:
interface Rect {
width: number;
height: number;
name: string;
}
interface Circular {
radius: number;
name: string;
}
function arean(arg: Rect | Circular) {
if (arg.name === 'Rect') {
return (arg as Rect).width * (arg as Rect).height;
}
if (arg.name === 'Circular') {
return Math.pow((arg as Circular).radius, 2) * Math.PI;
}
return 0;
}
arean({ name: 'Rect', width: 10, height: 10 });
arean({ name: 'Circular', radius: 10 });
单纯就这个例子可以改进:
interface Rect {
width: number;
height: number;
name: 'Rect';
}
interface Circular {
radius: number;
name: 'Circular';
}
function arean(arg: Rect | Circular) {
if (arg.name === 'Rect') {
return arg.width * arg.height;
}
if (arg.name === 'Circular') {
return Math.pow(arg.radius, 2) * Math.PI;
}
return 0;
}
arean({ name: 'Rect', width: 10, height: 10 });
arean({ name: 'Circular', radius: 10 });
typescript会自动做类型推断,有时候我们不需要去自己写类型:
let a = 1; // a:number
a = ''; // 不能将类型“string”分配给类型“number”
const B = 1; // B: 1
const C = {
a: 1,
b: 'b',
c: true,
}; // C: { a: number; b: string; c: boolean;}
C.b = 2; // 不能将类型“number”分配给类型“string”。
const A = {
a: 'a',
b: 'b',
/** 这是c属性 */
c: 'c',
};
A.c = 'aa'; // 提示:这是c属性
A.d = ''; //类型“{ a: string; b: string; c: string; }”上不存在属性“d”。
const B: { [key: string]: string } = {
a: 'a',
b: 'b',
c: 'c',
};
B.d = '';
上面例子A
推导出的类型是一个映射,vscode会有属性提示;而B
是一个{ [key: string]: string }
是没有固定属性的 所以没有提示 。
通过
/**
注释*/
注释的,在引用时vscode会显示注释
官方推荐用
interface
,其他无法满足需求的情况下用type
。实际应用中,定义接口用interface
,通过现有interface
的衍生使用type
。
// T类型在联合类型Keys中必选其一
export type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
T,
Exclude<keyof T, Keys>
> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
}[Keys];
interface A {
a: string;
b: boolean;
c: number;
}
const a: RequireAtLeastOne<A> = {
a: '',
};
const b: RequireAtLeastOne<A, 'a' | 'b'> = {
a: '',
c: 1,
};
// 获取T和R中的共有属性
export type ExtractCommon<T extends object, R extends object> = {
[K in NotNeverCommonKey<T, R>]: T[K];
};
type NotNeverCommonKey<T, R> = {
[K in keyof T & keyof R]: T[K] & R[K] extends never ? never : K;
}[keyof T & keyof R];
// test
type A = {
a: string;
c: number;
d: string;
};
type B = {
b: boolean;
c: string;
d: string;
};
type C = ExtractCommon<A, B>; // { d: string; }
ps: 不当之处,欢迎指正。