ts全类型梳理
前言
原始类型 primitive type
原始类型总共有七个:
let isDone: boolean = false; // 布尔类型
let age: number = 10; // 数字类型
let name: string = "jack"; // 字符串类型
let sym1: symbol = Symbol("key1"); // symbol类型
let bigint2: bigint = 12345678901234567890n; // bigint类型
let undefined1:undefined = undefined // undefined类型
let null:null = null // null类型
关于 null 和 undefined
null 类型对应的值只能是 js 的原始数据 null(空引用或者说空对象)。但是在不打开strictNullChecks
配置的情况下,null 和 undefined 两个类型被默认是其他类型的子类型。当然,bottom 类型never
除外,事实上 bottom type 才是所有类型的子类型。
// strictNullChecks: false
let n: null = null;
let un: undefined = undefined;
let a: string = n; // ok
let b: number = un; // ok
由于这两个类型被默认是其他类型的子类型,所以有个奇怪的现象,null 和 undefined 类型的数据,可以相互赋值。这在代码理解上总给人一种错乱感。所以比较建议打开strictNullChecks
配置。
// strictNullChecks: false
let n: null = null;
let un: undefined = undefined;
n = un; // ok
un = n; // ok
顶部类型 top type
首先明确两点,
- ts 是遵循里氏替换原则的,在 ts 中理解成子类实例可以赋值给父类实例。不过在极少数情况下,ts是会违反里氏替换规则的。
- js 中万事万物皆对象(可能undefined要除外)。即使
true
其实也是一个对象,它是Boolean
构造函数的一个实例。
ts 的 top type 有两个:any
和unknown
,这个 top 主要指,他们处于类型集合继承树的顶端,是所有类型的父类型。根据里氏替换原则子类型的数据可以赋值给父类型的数据,继而得出 定义成top type的数据可以被任意类型的数据赋值
any
any 表示任意类型,可以赋值任意类型,如果将数据定义为了 any 类型,那么就会失去 ts 的类型检查。
-
赋值方面:any 类型的数据,可以分配任何类型的值
let any1: any; any1 = 123; any1 = "";
-
使用方面:any 类型的数据,可以当做任何类型的数据来使用
let any1: any = 1; any1.push(1); // 当数组使用,ok
unknown
unknown 表示未知类型,TypeScript 3.0 引入的,它是 any 类型对应的安全类型,所谓的安全,主要体现在数据使用的时候。unknown
类型数据使用时,必须收敛成具体类型。
-
赋值方面 与 any 相同: unknown 类型的数据,可以分配任何类型的值
let unknown1: unknown; unknown1 = 123; unknown1 = "";
-
使用方面 与 any 不同: unknown 类型的数据,几乎不可以当做任何一种类型来使用。
let unknown1: unknown; unknown1.push(); // 当做数组,报错❌ let num: number = unknown1; // 当做数字,报错❌
-
unknown 类型数据使用前,必须使用类型断言
as
,typeof
判断,instanceof
判断等方式收敛为具体类型。let unk2: unknown; (unk2 as number).toFixed(2); let unk1: unknown; if (typeof unk1 === "number") unk1.toFixed(2); if (unk1 instanceof Number) unk1.toFixed(2);
-
unknown 类型联合任何类型,除 any 外结果都是 unknown 类型
type UnionType1 = unknown | string; // unknown type UnionType2 = unknown | any; // any
-
unknown 类型交叉任何类型,结果都是交叉的其他类型
type IntersectionType1 = unknown & string; // string type IntersectionType2 = unknown & any; // any
unknown
类型几乎可以全面替换any
类型。
底部类型 bottom type --- never
ts 的 bottom type 只有一个: never
。和 top 类型的含义一样,
bottom 指其处于类型集合继承树的底部,是所有类型的子类型。根据里氏替换原则,子类的值可以赋值给父类型的数据,继而得出 never
类型的数据可以赋值给任意类型的数据。因为没有子类型,never
类型的变量只能被never
类型数据赋值。
never
类型表示的含义是永远不可能拿到的类型,也代表了空联合或者或空集合。
-
赋值方面,
never
类型的变量只能分配never
类型的值。function throwError(message: string): never { throw new Error(message); } let never1: never = throwError("Something went wrong"); let num: number = never1; // ok,因为never是所有类型的子类型 never1 = num; // 报错❌,never1是never类型的数据,只能被赋值成never类型。
-
使用方面,作为所有类型的子类型,
never
类型的数据可以赋值给任意其他类型。不过在实际应用中,应该不会这样操作。function throwError(message: string): never { throw new Error(message); } let never1: never = throwError("Something went wrong"); let num: number = 123; num = never1; // ok,never类型数据可以当做任一类型使用 num.toFixed(2);
-
never
类型联合任何类型,结果都是联合的其他类型type UnionType1 = never | string; // string
-
never
类型交叉任何类型,结果都是never
type IntersectionType1 = never & string; // never type IntersectionType2 = never & any; // never
-
验证
never
是比较难的type IsNever<T> = T extends never ? true : false; type Res = IsNever<never>; // never
上述例子的主要问题其实在于
extends
关键字 extends关键字前面是联合类型的裸泛型时(注意,这里其实有四个触发条件, 1、在 extends 关键字前面;2、联合类型;3、裸;4、泛型 ),会隐式触发分布式 (将联合类型的每个子类型都应用到判断语句中得到一个子结果,最后再联合这些子结果)。 而never
被看做是空的联合类型,所以其会触发分布式,但是由于其联合内没有任何子类型,导致后续判断逻辑无法进行。所以extends never ? true : false
碰到never
类型的泛型T
时,相当于不执行,直接返回前者的T
,即never
。根据上述条件,解除隐式触发分布式即可。// 这里不再是裸泛型,同时元组也不是联合类型了。 type IsNever<T> = [T] extends [never] ? true : false; type Res = IsNever<never>; // true
-
never
常见使用场景之函数永不返回,当一个函数抛出异常、进入无限循环或根本不返回任何东西时,可以将其返回类型标记为never
。这样的函数被称为never-returning functions
function throwError(message: string): never { throw new Error(message); } function infiniteLoop(): never { while (true) { // do something indefinitely } }
-
never
常见使用场景之永不可能发生的情况:当某个代码分支永远不会被执行时,可以将其类型标记为never
。- eg1:函数逻辑分叉
function processValue(value: number | string): void { if (typeof value === "number") { // handle number case } else if (typeof value === "string") { // handle string case } else { const exhaustivenessCheck: never = value; throw new Error(`Unhandled value: ${exhaustivenessCheck}`); } } // ps:这里把never类型的数据赋值给了void,这个是没问题的,因为never类型数据可以赋值给任何数据。
- eg2:类型运算使用,配合条件判断隐式触发分布式和 never 在联合类型中的特殊性(联合类型中的 never 将被省略),收敛(narrowing)一些联合类型
type Exclude<Type, ToExcludedType> = Type extends ToExcludedType ? never : Type; // 排除Type中的ToExcludedType type Bc = Exclude<"a" | "b" | "c", "a">; // => // ("a" extends "a" ? never : "a")|("b" extends "a" ? never : "b")|("c" extends "a" ? never : "c") => // never | "b" | "c" => // "b" | "c"
- eg3:映射类型(从原对象类型产生新的对象类型),把对象相应的 key 转化成 never,从而达到过滤的效果。下例是从含有
a,b,c
三个键的对象类型中,过滤提取出a,b
属性,组成一个新的对象类型type ExtractMap<MapType extends {}, Keys> = { [key in keyof MapType as key extends Keys ? key : never]: MapType[key]; }; type ABCMap = { a: string; b: number; c: boolean }; type ABMap = ExtractMap<ABCMap, "a" | "b">; // => { a: string; b: number; never: boolean } // => { a: string; b: number; }
- eg1:函数逻辑分叉
特殊类型
上面的any
,unknown
,never
应该也属于特殊类型,不过他们有更具体的分类,所以就单独提取出去了。
void
void
在 js 和 ts 都是关键字,不过与上面的null
不一样,void
在 js 和 ts 上的含义和用法不太一样。
在 js 中,void
是一个运算符,它对紧跟其后的表达式求值。不管是什么表达式,void
总是返回真正的 undefined
。这也是为什么很多早期代码中用void 0
代表undefined
的原因
const un = void 0; // undefined
function handle1() {
return void 0;
}
用作自执行函数
void function fn() {
// doSomething
}()
在不使用分号的代码风格中,使用void
替代分号
let a = {}
void [9].push(1)
在 ts 中,void
主要应用在函数中,任何函数没有明确返回语句时,都会被推导为返回void
。同时它也是 undefined
的父类。
function fn() {} // => fn():void
type IsUndefinedExtendedFromVoid = undefined extends void ? true : false; // true
let un: undefined = undefined;
let void1: void = (() => {})();
void1 = un; // ok,根据里氏替换原则,子类型的数据(un)可以赋值给父类型的数据(void1)
上面例子中,基本体现不出来void
类型和undefined
类型的区别,其两者主要区别是用作函数返回值时,void
作为返回类型,函数实现时可以返回任意类型,注意,这里是一个违反里氏替换规则的情况。
let fn1: () => void = () => true; // ok,作为函数返回值时,void可以被true替换
let fn2: () => undefined = () => true; // 报错❌,不能将类型“boolean”分配给类型“undefined”
这个特性在内置函数中应该更好理解
//forEach类型定义: forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
//push类型定义: push(...items: T[]): number;
const src = [1, 2, 3];
const dst = [0];
src.forEach((el) => dst.push(el)); // ok,这里foreach定义的参数函数返回值是void类型,但是参数函数返回值其实是push函数的返回值 number类型。void作为函数返回类型时,可以被其他类型替换。
不过这里有一个特例,那就是在函数字面量(function
)定义的函数中显示要求返回void
时,紧随的函数体的返回值 就只能是void
类型了。
function fnWithFnBody1(): void {
return true; //报错❌,不能将类型“boolean”分配给类型“void”
}
const fnWithFnBody2 = function (): void {
return true; // 报错❌,不能将类型“boolean”分配给类型“void”
};
const fnWithFnBody3 = function (): void {
return undefined; // ok,返回子类型是不会报错的
};
但是函数被赋值或者使用箭头函数时,void 可被其他类型覆盖的特性仍然不受影响。
let arrowFn: () => void = () => {
// ok,箭头函数仍然可以使用string覆盖void
return "";
};
const fnReturnString = () => "123"; // () => string
let implicitFn = function () {}; // () => void
implicitFn = fnReturnString; // ok,隐式推断返回值为void的函数可以使用string覆盖void
let explicitFn = function (): void {}; // () => void, 显式声明返回类型为void
explicitFn = fnReturnString; // ok,显式声明返回类型为void的函数仍然可以使用string覆盖void
void 也可以用来指定 this 类型,从而避免某些函数对 this 的使用。
function clickFn(this: void) {
this.o = 0; // Property 'o' does not exist on type 'void'.
}
document.addEventListener("click", clickFn);
Object
- Object 包含除
any
,unknown
,void
,undefined
和null
外的所有类型,也可以认为成包含了所有的非空类型。
let o: Object;
o = { prop: 0 }; //object
o = []; // array
o = 42; // number
o = "string"; // sting
o = false; // boolean
o = Symbol("key1"); // symbol
o = 12345678901234567890n; // bigint
o = Math.random; // function
同时它还有一些内建的方法
interface Object {
/** The initial value of Object.prototype.constructor is the standard built-in Object constructor. */
constructor: Function;
/** Returns a string representation of an object. */
toString(): string;
/** Returns a date converted to a string using the current locale. */
toLocaleString(): string;
/** Returns the primitive value of the specified object. */
valueOf(): Object;
/**
* Determines whether an object has a property with the specified name.
* @param v A property name.
*/
hasOwnProperty(v: PropertyKey): boolean;
/**
* Determines whether an object exists in another object's prototype chain.
* @param v Another object whose prototype chain is to be checked.
*/
isPrototypeOf(v: Object): boolean;
/**
* Determines whether a specified property is enumerable.
* @param v A property name.
*/
propertyIsEnumerable(v: PropertyKey): boolean;
}
let o: Object = {};
o.toString(); // built-in method => string
通过 new Object()
创建的变量,将被认为是 Object
类型
let obj = new Object(); // Object
重写内建方法,且与原始内建方法定义冲突时,会报错。
let obj: Object = {
toString() {
// 报错❌,不能将类型“number”分配给类型“string”
return 123;
},
};
{}
{}
和Object
包含的范围是一致的,两者只有三点区别
{}
类型不能通过new
关键字创建keyof
处理Object
会读取到其内置方法,处理{}
和object
时会当没有key处理。- 重写内建方法,且与原始内建方法定义冲突时,不会报错。
let obj: {} = { toString() { // OK return 123; }, };
{}
一般被用来过滤null
和undefined
/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T & {};
object
object
在{}
的基础(不包含null
和undefined
)上,排除了剩余的原始类型(string
, number
, bigint
, boolean
, symbol
),一般使用时,把它理解成 空对象({}) 的类型就可以了。当然对于一些特殊的对象,数组和函数,它也是包含的。
let obj: object = {
toString() {
// OK
return 123;
},
};
obj = 1; //报错❌, 不能将类型“number”分配给类型“object”
obj.valueOf(); // OK
obj = () => {}; // OK
obj = [1, 2, 3]; // OK
Function
可以认为是所有函数类型的父类型,其本身可以看作参数和返回值都是 any 的函数类型
type FnExtendedFormFunction = (() => void) extends Function ? true : false; // true
let fn: Function; // (...arg: any) => any;
对象类型
上面的Object
,{}
,object
其实也属于对象类型。讲对象类型之前,请先确保你已经知道类型定义的两种方法。我们一般使用type
或者interface
关键字来定义对象类型。
interface Person {
name: string;
age: number;
}
type Person = {
name: string;
age: number;
};
当然,直接写字面量也行
let obj: { a: string; b: number };
对象表示树类型,直接递归即可:
type Tree = { value: string; left: Tree; right: Tree };
对象类型赋值
使用字面量给对象类型赋值时,其会违反里氏替换原则。
let obj:{name:string} = {name:'',age:18} // 报错❌,You can't pass property `age` to type `{ name: string; }`
function fn(obj: { name: string }) {}
fn({ name: "", age: 18 }); // 报错❌,You can't pass property `age` to type `{ name: string; }`
使用变量赋值则没有问题
const obj1= {name: 'sr', age: 18}
let obj: { name: string } =obj1 // ok
function fn(obj: { name: string }) {}
fn(obj1); // ok
类型修饰符 readonly
一般来说,对象中定义的键值对,其值是可以修改的,不过总有一些场景,我们不希望值被修改(例如将一个对象当做参数传入函数中时),这个时候可以使用readonly
修饰符来修饰这个键值对,将其变成只读属性:
interface SomeType {
readonly staticProp: string;
normalProp: string;
}
let a: SomeType = { staticProp: "staticProp", normalProp: "normalProp" };
a.staticProp = ""; // 报错❌, 无法为“staticProp”赋值,因为它是只读属性
a.normalProp = ""; // ok
和上面例子相反,有些场景下,我们需要修改一些只读属性的值,例如拷贝了一个对象,要修改这个新对象中的数据时。那就需要使用-readonly
在类型中移除只读限制。
// 这个例子用到了泛型和循环对象类型的一些知识点,可以后续再看。
type CreateMutable<Type> = {
// 通过'-'移除Type键值对中的readonly
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
类型修饰符 ?
?
修饰符代表的含义是,对象中该键值对是可选(非必须)地。该修饰符在做一些配置项时特别实用。
interface Config {
id: string;
name: string;
description?: string; // 可选属性
options?: {
enabled: boolean;
timeout?: number; // 嵌套的可选属性
};
}
const config1: Config = {
id: "123",
name: "My Config",
description: "This is a config",
options: {
enabled: true,
timeout: 5000,
},
};
const config2: Config = {
id: "456",
name: "Another Config",
};
ts 内置类型别名Partial
就是通过?
实现的
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
同样地,也可以用-
移除可选修饰符?
,内置类型别名Required
实现如下
/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
索引签名 Index Signatures
很多情况下我们并不能一次性描述所有的 key,例如伪字符串数组,只知道它的成员都是 string 类型的,有一个叫做 length 的成员,它的值是 number 类型
interface StringArray {
length: number;
[index: number]: string; // 这里的index可以改成任意名称,只是一个变量代指所有的number类型的key
}
固定项符合索引签名规则时,其类型也必须符合索引类型值的限制。
interface Obj {
length: number; // 报错❌,'length'符合索引签名([key: string]),其值的类型必须符合索引类型的限制, 即 boolean类型
[key: string]: boolean;
}
数组类型
在 ts 中,数组内的成员被要求只能是同一种类型。例如字符串数组,数字数组,某种对象数组等,其定义形式有两种。
-
使用数组泛型
Array<T>
定义interface Person { name: string; age: number; } type StringArray = Array<string>; type NumberArray = Array<number>; type PersonArray = Array<Person>;
-
使用
[]
字面量定义interface Person { name: sting; age: number; } type StringArray = string[]; type NumberArray = number[]; type PersonArray = Person[];
只读数组 ReadonlyArray
除了数组类型外,ts 还提供了只读数组类型。只读数组类型的数据,没有内建修改数组的方法,例如 push
,pop
,shift
,unshift
等,数组成员本身不允许增删和覆盖。
- 使用只读数组泛型
ReadonlyArray
定义interface Person { name: string; age: number; } type StringArray = ReadonlyArray<string>; type NumberArray = ReadonlyArray<number>; type PersonArray = ReadonlyArray<Person>; let p: PersonArray = [{ name: "jack", age: 18 }]; p[0] = { name: "jack", age: 18 }; // 报错❌, 类型“PersonArray”中的索引签名仅允许读取。数组成员本身不允许增删和覆盖。 p[0].name = "tom"; // ok,这个其实和readonlyArray没有关系了,属于改变数组成员内部的值,只和数组成员定义的类型有关
- 使用字面量
readonly Type[]
定义interface Person { name: string; age: number; } type StringArray = readonly string[]; type NumberArray = readonly number[]; type PersonArray = readonly Person[];
从继承上,可以认为,非只读类型继承了只读类型,并扩展了一些内建方法,如push
,修改成员项接口等
type ReadOnlyT = readonly string[];
type NormalT = string[];
type IsNormalTExtendedFromReadOnlyT = NormalT extends ReadOnlyT ? true : false; // true
根据里氏替换原则,子类数据可以赋值给父类数据。即非只读数据可以赋值给只读数据,反之则不能。
let readonlyData: readonly string[] = [];
let normalData: string[] = [];
readonlyData = normalData; // ok
normalData = readonlyData; // 报错❌,类型 "readonly string[]" 为 "readonly",不能分配给可变类型 "string[]"
元组 Tuple
元组可以认为是长度固定,且内部成员类型明确的数组,内部成员间类型可以不同。
type StringNumberPair = [string, number];
只读元组
和只读数组类似,ts 还提供了只读元组类型。语法也和只读数组字面量语法类似。
type LockedStringNumberPair = readonly [string, number];
只读元组和非只读元组之间也符合上述只读类型是非只读类型父类型的原则
type StringNumberPair = [string, number];
type LockedStringNumberPair = readonly [string, number];
let stringNumberPair: StringNumberPair = ["1", 2];
let lockedStringNumberPair: LockedStringNumberPair = ["1", 2];
lockedStringNumberPair = stringNumberPair; // ok, 非只读类型可以赋值给只读类型
stringNumberPair = lockedStringNumberPair; // 报错❌,类型 "LockedStringNumberPair" 为 "readonly",不能分配给可变类型 "StringNumberPair"
函数类型
这里只对函数类型定义做一些简单讲解,其他像可选参数,剩余参数,函数重载,逆变协变等将在后续文章中展开。
// 接口定义
interface IFn {
(a: string, b: number): number; // 冒号后面为返回值
}
// 类型别名定义
type IFn2 = (a: string, b: number) => number; // 纯类型时箭头后面是返回值。
使用function
关键字定义,有类型推断的存在,写成下文fn1
的形式,明确必要类型就可以了
// 明确参数类型
function fn1(a: string, b: number) {
return a.length + b;
}
// 明确参数和返回值类型。杂糅在函数体中时,返回值在小括号后面,用 :Type 表示
function fn2(a: string, b: number): number {
return a.length + b;
}
使用箭头函数定义,有类型推断的存在,写成下文fn1
的形式,明确必要类型就可以了
// 明确参数类型
const fn1 = (a: string, b: number) => a.length + b;
// 明确参数和返回值类型。 杂糅在函数体中时,返回值在小括号后面,用 :Type 表示
const fn2 = (a: string, b: number): number => a.length + b;
// 明确变量 fn3 的类型
const fn3: (a: string, b: number) => number = (a, b) => a.length + b;
// 明确 变量fn4、参数和返回值 的类型
const fn4: (a: string, b: number) => number = (a: string, b: number): number =>
a.length + b;
class
本小节主要讲类的类型,并不会对类其他方面做过多介绍。
-
当定义一个类的时候,实例的类型是类本身
class Person { public name: string; public age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } const person = new Person("tom", 18); person.name; // string person.age; // number
-
而要获取类本身的类型需得通过
typeof
关键字class Person { public name: string; public age: number; static staticKey: number = 0; constructor(name: string, age: number) { this.name = name; this.age = age; } } const person = new Person("tom", 18); person.name; // string person.age; // number person.staticKey; // 报错❌, 属性“staticKey”在类型“Person”上不存在 type PersonType = typeof Person; type PersonKeys = keyof PersonType; // "prototype" | "staticKey"
-
一个不需要参数的类构造函数的类型为
new () => T
,其实需要参数在相应位置填入参数即可。class Person { public name: string = ""; public age: number = 0; } function createInstance<T>(ctor: new () => T): T { return new ctor(); } const person = createInstance(Person); // Person
其他类对象类型
其他类对象类型,例如map
,set
,promise
等,和Array<T>
泛型类似,都是通过内建相应的Map<K, V>
,Set<T>
,Promise<T>
泛型实现的。
字面量类型 Literal Types
我们并不需要总是为数据绑定类型,ts 系统在很多情况下可以根据类型定义推断出数据的类型,它甚至专门为推断定义了一个关键字infer
let str = "abc"; // string
let bool = true; // boolean
let num = 123; // number
let obj = { name: "jack", age: 18 }; // { name: string; age: number }
type ReturnType<T extends Function> = T extends (...args: any) => infer R // infer后续会详细讲解
? R
: never;
type FnReturnType = ReturnType<() => string>; // string
在 js 中,使用const
定义变量,是不允许再被重新赋值的,我们一般称呼其为常量。而在 ts 中,通过const
定义的 原始类型数据 会被推断成相应字面量的类型。
let str = "abc"; // string
const str2 = "abc"; // 'abc'
const bool = true; // true
const num = 123; // 123
// 非原始数据不会被推断为字面量类型
const obj = { name: "jack", age: 18 }; // { name: string; age: number }
字面量推断
除了推断原始类型为字面量类型外,ts 其实也可以推断出字面量对象和字面量元组,但是要借助as const
这两个关键字。并且推断出来的对象属性将直接变成只读属性。元组则直接变成只读元组。
const obj = { name: "jack", age: 18 } as const; // { readonly name: "jack"; readonly age: 18 }
const tuple1 = ["jack", 18] as const; // readonly ["jack", 18];
类型定义的两种方法
专门用来定义类型的关键字有两个type
和interface
。类型定义,建议首字母大写
类型别名 type
开发过程中我们可能会复用一些类型,例如两个函数的参数都要用到相同的参数类型。这个时候,我们会希望将这个类型定义成一个类似 js 中变量的形式,方便在不同的地方复用这个类型。而在 ts 中我们称这个类似 js 变量的 ts 变量为类型别名(Type Aliases),它由 type
关键字定义。
type ParamType = { name: string; age: number };
function fn1(arg: ParamType) {}
function fn2(arg: ParamType) {}
接口 interface
对象类型的定义一般由两种方式,一种是上文展示的使用type
关键字定义,还有一种就是使用interface
关键字定义。
interface ParamType {
name: string;
age: number;
}
type 和 interface 的相同点
- 都可以创建对象类型
- 都可以拓展对象类型
type 和 interface 的不同点
-
interface
不能定义原始类型数据,但type
可以 -
interface
可以给已存在的类型添加属性,但type
不行interface Person { name: string; } interface Person { age: number; } let person: Person; person.name; // string person.age; // number
-
两者拓展对象类型的方式不同
// interface 通过extends关键字 interface Animal { name: string; } interface Bear extends Animal { honey: boolean; } let bear: Bear; bear.name; // string bear.honey; // boolean
// type 通过交叉类型操作符 & type Animal = { name: string; }; type Bear = Animal & { honey: boolean; }; let bear: Bear; bear.name; // string bear.honey; // boolean
-
在报错提示中,如果数据不符合接口相关定义,会提示到具体不符合哪个接口的定义。但如果不符合类型别名的定义,则错误提示体现不出来具体违背了哪个类型别名的定义(这个应该是因编辑器而异的,vscode 上两者都能定位到。)
interface Mammal { name: string; } function echoMammal(m: Mammal) { console.log(m.name); } // 报错信息会提示违背了接口Mammal.name的类型定义 echoMammal({ name: 12343 });
type TMammal = { name: string }; function echoAnimal(m: TMammal) { console.log(m.name); } // 某些编辑器上,报错信息只会提示违背了某个属性name的定义,但是不会提现TMammal echoAnimal({ name: 12345 });
在两者使用上,typescript handbook 的建议是尽量使用
interface
后记
参考资料
- A Complete Guide To TypeScript's Never Type
- TS 中何时使用“never”与“unknown”类型(20190724)
- The TypeScript Handbook
- Making sense of TypeScript using set theory
- The Type Hierarchy Tree
- An Introduction To Type Programming In TypeScript
- void in JavaScript and TypeScript
- Conditional Types in TypeScript
- Liskov substitution principle
- typescript type 分配条件类型
转载自:https://juejin.cn/post/7304267413615706139