type
说明
type又叫类型别名(type alias),作用是给一个类型起一个新名字,不仅支持interface定义的对象结构,还支持基本类型、联合类型、交叉类型、元组等任何你需要手写的类型如下代码所示:
type userName = string; // 基本类型
type userId = string | number; // 联合类型
type arr = number[];
// 对象类型
type Person = {
id: userId; // 可以使用定义类型
name: userName;
age: number;
gender: string;
isWebDev: boolean;
};
// 范型
type Tree<T> = {
value: T };
const user: Person = {
id: "1",
name: "张三",
age: 10,
gender: "女"
};
const numbers: arr = [1, 2 , 3];
接口 interface
说明
接口是命名数据结构(例如对象)的另一种方式;与type 不同,interface仅限于描述对象类型。
接口的声明语法也不同于类型别名的声明语法。让我们将上面的类型别名 Person 重写为接口声明:
interface Person {
id: userId;
name: userName;
age: number;
gender: string;
}
interface 和 type 的区别
- 接口可以扩展,但 type 不能 extends 和 implement 。
接口可以扩展,但type不能extends和implement。不过,type可以通过交叉类型实现interface的extends行为。interface 可以extends type,同时type也可以与interface类型交叉。
接口扩展
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
let student:User={
name:'wang',age:10}
type 交叉
type Name = {
name: string
}
type User = Name & {
age: number };
let student:User={
name:'张三',age:20}
interface 扩展 type
type Name = {
name: string;
}
interface User extends Name {
age: number;
}
let student:User={
name:'张三',age:20}
type 与 interface 交叉
interface Name {
name: string;
}
type User = Name & {
age: number;
}
let student:User={
name:'张三',age:20}
- 声明合并
type包含了interface大部分的能力,但声明合并这个能力只有interface支持。
我们可以声明同名的interface,它们会自动合并。但type不可以声明同名的类型,就像ES6里我们不能用let或const重复声明变量。如下代码所示:
interface Dog {
name: 'dog';
}
interface Dog {
run: () => void;
}
const dog: Dog = {
name: 'dog',
run: () => {
console.log('dog run');
}
};
// Duplicate identifier 'Fish'.
type Fish = {
name: 'fish';
}
// Duplicate identifier 'Fish'.
type Fish = {
swim: () => void;
}
- 冲突处理
interface生成一个打平的对象类型来检测属性的冲突,如果有冲突则报错。而type只是递归地合并属性,并不会在声明的时候报错,指定值的类型时则可能会报错,也可能不会报错。
interface DateObj {
value: string;
}
// 报错
// Interface 'DateObj1' incorrectly extends interface 'DateObj'.
// Types of property 'value' are incompatible.
// Type 'Date' is not assignable to type 'string'.
interface DateObj1 extends DateObj {
value: Date;
}
type DateObj2 = {
value: string;
} & {
value: Date;
}
// 声明值的类型时报错
// Type 'string' is not assignable to type 'string & Date'.
// Type 'string' is not assignable to type 'Date'.
let foo: DateObj2 = {
value: '' };
type Person = {
getPermission: (id: string) => string;
};
type Staff = Person & {
getPermission: (id: string[]) => string[];
};
// 不会报错
const AdminStaff: Staff = {
getPermission: (id: string | string[]) =>{
return (typeof id === 'string'? 'admin' : ['admin']) as string[] & string;
}
}
type声明对象的交叉类型有时会产出never,有的时候不会,比如前面的DateObj2就没有产出never。
对象类型的交叉类型T1 & T2 & … & Tn要产出never,需要满足以下几个条件:
- 两个或多个Tx类型有相同名字的属性;
- 至少有一组同名的属性,某个类型比如T1的这个属性是字面量类型且所有Tx类型的这个属性都不是never;
- 至少一个属性交叉之后的结果是never。
- index签名
interface默认是没有index签名的,而type有。
什么是index签名?对于对象或者数组,我们可以通过index签名描述所有value的类型,形式如下:
interface IStringArray {
[index: number]: string;
}
interface IStringObject {
[index: string]: string;
}
type TStringArray = {
[index: number]: string;
}
type TStringObject = {
[index: string]: string;
}
index可以换成任意字面量如K、P等。
然后,看下面的例子:
interface Animal {
name: string;
}
function log(obj: Record<string, unknown>) {
console.log(obj);
}
const dog: Animal = {
name: 'dog' };
// 报错
// Argument of type 'Animal' is not assignable to parameter of type 'Record<string, unknown>'.
// Index signature for type 'string' is missing in type 'Animal'.
log(dog);
一种解决办法是给interface Animal增加index签名:
interface Animal {
[index: string]: unknown;
name: string;
}
另一种办法是改成type写法:
type Animal = {
name: string;
}
- 是否支持映射类型
nterface是不支持映射类型写法的,而type支持。以下写法都是不允许的:
interface X {
[P in 'A' | 'B']: string;
}
interface StringToBoolean<T extends Record<string, string>> {
[P in keyof T]: boolean;
}
改成type就没问题:
type X = {
[P in 'A' | 'B']: string;
}
type StringToBoolean<T extends Record<string, string>> = {
[P in keyof T]: boolean;
}
总结:
使用interface的场景:
- 当需要定义对象的结构和方法签名时,interface是首选。它主要用于描述对象应该具有哪些属性和方法,但不限制具体的实现。例如,在定义React组件的props时,使用interface可以确保组件的使用者不能随意添加未声明的属性,从而保持组件的稳定性和可预测性;
- 在编写三方库时,interface也是推荐的选择,因为它提供了更灵活的类型合并,能够应对未知的复杂使用场景。
- interface还支持继承和实现,可以通过继承已有的接口或类来扩展新的接口定义,这种特性在需要构建复杂的类型关系时非常有用。
使用type的场景:
- type几乎涵盖interface的所有能力,不能使用interface声明的类型都使用type,比如基础类型、联合类型、元组等;
- 当需要定义更复杂的类型,如联合类型、交叉类型等,type是一个更好的选择。它提供了更高级的类型操作能力,适用于需要组装或创建类型别名的场景;
- 在某些情况下,如果代码已经统一选择使用type来定义类型,那么为了保持代码的一致性,可以继续使用type来定义类型;
- type不支持类的实现和继承,但它的可合并性较弱,不支持自动合并同名接口,这在某些情况下可能是一个限制;
最近搞了一个前端知识分享的群,大家有什么问题都可以在里面交流,里面会不定期更新,欢迎大家的加入