TypeScript 与 JavaScript 的区别
| TypeScript | JavaScript |
|---|---|
| JavaScript 的超集用于解决大型项目的代码复杂性 | 一种脚本语言,用于创建动态网页 |
| 可以在编译期间发现并纠正错误 | 作为一种解释型语言,只能在运行时发现错误 |
| 强类型,支持静态和动态类型 | 弱类型,没有静态类型选项 |
| 最终被编译成 JavaScript 代码,使浏览器可以理解 | 可以直接在浏览器中使用 |
| 支持模块、泛型和接口 | 不支持模块,泛型或接口 |
| 社区的支持仍在增长,而且还不是很大 | 大量的社区支持以及大量文档和解决问题的支持 |
它是JavaScript的一个超集,添加了可选的静态类型和面向对象编程
类型检查可以在运行时做,也可以运行之前的编译期做。这是两种不同的类型,前者叫做动态类型检查,后者叫做静态类型检查。
两种类型检查各有优缺点。动态类型检查 在源码中不保留类型信息,对某个变量赋什么值、做什么操作都是允许的,写代码很灵活。但这也埋下了类型不安全的隐患,比如对 string 做了乘除,对 Date 对象调用了 exec 方法,这些都是运行时才能检查出来的错误。
其中,最常见的错误应该是 “null is not an object”、“undefined is not a function” 之类的了,写代码时没发现类型不匹配,到了运行的时候才发现,就会有很多这种报错。
所以,动态类型虽然代码写起来简单,但代码中很容易藏着一些类型不匹配的隐患。

静态类型检查则是在源码中保留类型信息,声明变量要指定类型,对变量做的操作要和类型匹配,会有专门的编译器在编译期间做检查。
静态类型给写代码增加了一些难度,因为你除了要考虑代码要表达的逻辑之外,还要考虑类型逻辑:变量是什么类型的、是不是匹配、要不要做类型转换等。
不过,静态类型也消除了类型不安全的隐患,因为在编译期间就做了类型检查,就不会出现对 string 做了乘除,调用了 Date 的 exec 方法这类问题。
所以,静态类型虽然代码写起来要考虑的问题多一些,会复杂一些,但是却消除了代码中潜藏类型不安全问题的可能。

动态类型只适合简单的场景,对于大项目却不太合适,因为代码中可能藏着的隐患太多了,万一线上报一个类型不匹配的错误,那可能就是大问题。
而静态类型虽然会增加写代码的成本,但是却能更好的保证代码的健壮性,减少 Bug 率。
所以,大型项目注定会用静态类型语言开发
TypeScript 变量声明
变量是一种使用方便的占位符,用于引用计算机内存地址。
我们可以把变量看做存储数据的容器。
TypeScript 变量的命名规则:
-
变量名称可以包含数字和字母。
-
除了下划线 _ 和美元 $ 符号外,不能包含其他特殊字符,包括空格。
-
变量名不能以数字开头。
cannot be compiled under ‘--isolatedModules‘ because it is considered a global script file. Add an i
找到tsconfig.json的配置文件:
isolatedModules字段改为false
原因:
Typescript将没有导入/导出的文件视为旧脚本文件。这样的文件不是模块,它们的任何定义都已合并到全局名称空间中。 isolatedModules禁止此类文件。
将任何导入或导出添加到文件都使其成为一个模块,并且错误消失。
export {}也是一种方便的方法,可以在不导入任何内容的情况下使文件成为模块(定义变量的时候不会有冲突)。不会有如下的问题:
注意:变量不要使用 name 否则会与 DOM 中的全局 window 对象下的 name 属性出现了重名。
any 类型
任意值是 TypeScript 针对编程时类型不明确的变量使用的一种数据类型,它常用于以下三种情况。
1、变量的值会动态改变时,比如来自用户的输入,任意值类型可以让这些变量跳过编译阶段的类型检查,示例代码如下:
let x: any = 1; // 数字类型 x = 'I am who I am'; // 字符串类型 x = false; // 布尔类型
改写现有代码时,任意值允许在编译时可选择地包含或移除类型检查,示例代码如下:
let x: any = 4; x.king(); // 正确,king方法在运行时可能存在,但这里并不会检查 x.toFixed(); // 正确
定义存储各种类型数据的数组时,示例代码如下:
let arrayList: any[] = [1, false, 'fine']; arrayList[1] = 100;
Null 和 Undefined
null
在 JavaScript 中 null 表示 "什么都没有"。
null是一个只有一个值的特殊类型。表示一个空对象引用。
用 typeof 检测 null 返回是 object。
undefined
在 JavaScript 中, undefined 是一个没有设置值的变量。
typeof 一个没有值的变量会返回 undefined。
Null 和 Undefined 是其他任何类型(包括 void)的子类型,可以赋值给其它类型,如数字类型,此时,赋值后的类型会变成 null 或 undefined。而在TypeScript中启用严格的空校验(--strictNullChecks)特性,就可以使得null 和 undefined 只能被赋值给 void 或本身对应的类型,示例代码如下:
// 启用 --strictNullChecks let x: number; x = 1; // 运行正确 x = undefined; // 运行错误 x = null; // 运行错误
上面的例子中变量 x 只能是数字类型。如果一个类型可能出现 null 或 undefined, 可以用 | 来支持多种类型,示例代码如下:
// 启用 --strictNullChecks let x: number | null | undefined; x = 1; // 运行正确 x = undefined; // 运行正确 x = null; // 运行正确
never 类型
never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。这意味着声明为 never 类型的变量只能被 never 类型所赋值,在函数中它通常表现为抛出异常或无法执行到终止点(例如无限循环),示例代码如下:
let x: never; let y: number; // 运行错误,数字类型不能转为 never 类型 x = 123; // 运行正确,never 类型可以赋值给 never类型 x = (()=>{ throw new Error('exception')})(); // 运行正确,never 类型可以赋值给 数字类型 y = (()=>{ throw new Error('exception')})(); // 返回值为 never 的函数可以是抛出异常的情况 function error(message: string): never { throw new Error(message); } // 返回值为 never 的函数可以是无法被执行到的终止点的情况 function loop(): never { while (true) {} }
断言
1+true == 2 在ts环境 报错,在js可以
使用断言可以使等式成立 类型断言并不意味着,可以把某个值断言为任意类型。
const n = 1;
const m:string = n as string; // 报错
上面示例中,变量n是数值,无法把它断言成字符串,TypeScript 会报错。
类型断言的使用前提是,值的实际类型与断言的类型必须满足一个条件。
expr as T
上面代码中,expr是实际的值,T是类型断言,它们必须满足下面的条件:expr是T的子类型,或者T是expr的子类型。
也就是说,类型断言要求实际的类型与断言的类型兼容,实际类型可以断言为一个更加宽泛的类型(父类型),也可以断言为一个更加精确的类型(子类型),但不能断言为一个完全无关的类型。
但是,如果真的要断言成一个完全无关的类型,也是可以做到的。那就是连续进行两次类型断言,先断言成 unknown 类型或 any 类型,然后再断言为目标类型。因为any类型和unknown类型是所有其他类型的父类型,所以可以作为两种完全无关的类型的中介。
// 或者写成 <T><unknown>expr
expr as unknown as T
上面代码中,expr连续进行了两次类型断言,第一次断言为unknown类型,第二次断言为T类型。这样的话,expr就可以断言成任意类型T,而不报错。
下面是本小节开头那个例子的改写。
const n = 1;
const m:string = n as unknown as string; // 正确
上面示例中,通过两次类型断言,变量n的类型就从数值,变成了完全无关的字符串,从而赋值时不会报错。
TypeScript中的 ?: 是什么意思
可选参数和可选属性
使用了 –strictNullChecks,可选参数会被自动地加上 | undefined:
function f(x: number, y?: number) {
return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // error, 'null' is not assignable to 'number | undefined'
class C {
a: number;
b?: number;
}
let c = new C();
c.a = 12;
c.a = undefined; // error, 'undefined' is not assignable to 'number'
c.b = 13;
c.b = undefined; // ok
c.b = null; // error, 'null' is not assignable to 'number | undefined'
使用泛型(限制为 extends object)与直接使用 object 类型的区别?
使用具体类型的 printName 函数
interface Named {
name: string;
}
// 使用具体类型的函数
function printNameSpecific(obj: Named) {
console.log(obj.name); // 直接访问 obj.name,因为 obj 是 Named 类型
}
// 使用示例
const person: Named = { name: 'Alice' };
printNameSpecific(person); // 输出: Alice
// 如果尝试传递一个非 Named 类型的对象,TypeScript 会报错
// const notNamed = { title: 'Mr.' };
// printNameSpecific(notNamed); // 类型错误
在这个例子中,printNameSpecific 函数只能接受 Named 类型的对象,并且只能访问该类型定义的属性。如果尝试传递一个不符合 Named 接口的对象,TypeScript 编译器会报错,从而保证了类型安全。
使用泛型的 printName 函数
interface Named {
name: string;
}
interface Employee extends Named {
id: number;
department: string;
}
// 使用泛型的函数
function printNameGeneric<T extends Named>(obj: T) {
console.log(obj.name); // 直接访问 obj.name,因为 T 被约束为 Named
// 可以在函数内部进行额外的类型检查或利用 T 的其他属性(如果适用)
if ('id' in obj && 'department' in obj) {
console.log(`Employee ID: ${obj.id}, Department: ${obj.department}`);
}
}
// 使用示例
const person: Named = { name: 'Alice' };
printNameGeneric(person); // 输出: Alice
const employee: Employee = { name: 'Bob', id: 123, department: 'Engineering' };
printNameGeneric(employee); // 输出: Bob 和 Employee ID/Department 信息
// 也可以传递其他符合 Named 接口的对象
const customer: { name: string; email: string } = { name: 'Charlie', email: 'charlie@example.com' };
printNameGeneric(customer); // 输出: Charlie,但不会输出 email
在这个泛型版本的例子中,printNameGeneric 函数更加灵活,因为它可以接受任何符合 Named 接口的对象。这意呀着,只要对象具有 name 属性,无论它是否还有其他属性,都可以传递给这个函数。
此外,由于泛型的使用,我们可以在函数内部利用 T 的类型信息(尽管在这个例子中我们主要使用了 Named 接口的属性),并且可以在需要时添加额外的类型检查来访问特定类型的属性(如 Employee 的 id 和 department)。
ps: 泛型的相关知识
ps:
高级类型介绍
在实际开发中,大家可能比较常用一些基础类型,比如string、number、boolean等,但是当我们了解了一些高级类型后,我们可以定义更大复杂,更加灵活的接口类型。
1. 联合类型(|)
联合类型的规则和逻辑是一致的,表示类型是连接多个类型中的任意一个
T | U
// demo
interface IPerson {
age: number;
gender: '女' | '男';
}
const person: Iperson = {
age: 25;
gender: '女'
}
2. 交叉类型(&)
交叉类型可以将多个类型合并成一个类型,写法和逻辑相同
T & U
// demo 现在有两个类,我们可以通过交叉类型来实现一个新的属性的类型定义
interface IPerson {
age: number;
gender: string;
}
interface IJob {
title: string;
years: number;
}
const life: IPerson & IJob = {
age: 30,
gender: 'nv',
title: '开发',
years: 10,
};
3. 类型别名(type)
类型别名,它允许你为类型创建一个名字,这个名字就是类型的别名,从而你可以在多处使用这个别名,并且有必要的时候,你可以更改别名的值(类型),以达到一次替换,多处应用的效果,类型别名与声明变量的语法很类似,只需要把const,let换成type关键字即可。
type Alias = T | U
// demo
type sex = '女' | '男';
interface IPerson {
age: number;
gender: sex;
}
4. 类型索引(keyOf)
keyof 类似于 Object.keys ,用于获取一个接口中 Key 的联合类型
// demo
interface IPerson {
age: number;
gender: sex;
}
// 使用了keyOf后,只要IPerson修改了,type类型也会跟着自动修改
type personKeys = keyof IPerson;
//等价于
type personKeys = 'age' | 'gender';
5. 类型约束(extends)
extends主要是用来对泛型加以约束的,他不像class使用extends是为了达到继承的目的。
// demo
type BaseType = string | number | boolean;
public testGenerics<T extends BaseType>(arg: T): T {
return arg;
}
this.testGenerics('123'); // 成功
this.testGenerics({}); // 失败
extends的应用场景
extends 经常与 keyof 一起使用,例如我们有一个方法专门用来获取对象的值,但是这个对象并不确定,我们就可以使用 extends 和 keyof 进行约束,具体例子如下:
// 根据传入的obj来约束key的值
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
ps:在 typeScript 在不同的上下文中,extends 有以下几个语义。不同语义即有不同的用途:
- 用于表达类型组合;
- 用于表达面向对象中「类」的继承
- 用于表达泛型的类型约束;
- 在条件类型(conditional type)中,充当类型表达式,用于求值。
6. 条件类型(U?X:Y)
条件类型的语法规则和三元表达式一致,一般用于一些类型不确定的情况
T extends U ? X : Y // 如果T是U的子级 ,那么他的类型就是X,否则就是Y
type Extract<T, U> = T extends U ? T : never; // 如果T是U的子级,那么返回T,否则抛弃
我们来看一下内置属性
,他是用来提取公共属性的,接下来看一下实例:
interface ITeacher {
age: number;
gender: sex;
}
interface IStudent {
age: number;
gender: sex;
homeWork: string;
}
type CommonKeys = Extract<keyof ITeacher, keyof IStudent>; // "age" | "gender"
7. 类型映射(in)
in用来做类型映射,遍历已有接口的key或者遍历联合类型
type Test<T> = {
[P in keyof T]: T[P];
};
// keyof T 相当于 type ObjKeys = 'a' | 'b'
// P in ObjKeys 相当于执行了一次 forEach 的逻辑,遍历 'a' | 'b'
interface IObj {
a: string;
b: string;
}
type newObj = Test<IObj>;
type和interface的区别
类可以实现interface 以及 type(除联合类型外)
类无法实现联合类型
type Person = { name: string; } | { setName(name:string): void };
// 无法对联合类型Person进行实现
// error: A class can only implement an object type or intersection of object types with statically known members.
class Student implements Person {
name= "张三";
setName(name:string):void{// todo}
}
索引签名问题
如果你经常使用TypeScript, 一定遇到过相似的错误:
Type ‘xxx’ is not assignable to type ‘yyy’
Index signature is missing in type ‘xxx’.
看个例子来理解问题:
interface propType{[key: string] : string
}
let props: propType
type dataType = {title: string
}
interface dataType1 {title: string
}
const data: dataType = {title: "订单页面"}
const data1: dataType1 = {title: "订单页面"}
props = data
// Error:类型“dataType1”不可分配给类型“propType”; 类型“dataType1”中缺少索引签名
props = data1
原因:interface定义的类型是不确定的, 后面再来一个:
interface propType{
title:number
}
这样propType类型就被改变了。
结论:
接口与类型别名的区别 type可以表示非对象类型,而interface只能表示对象类型;
interface可以继承type、class、interface,而type不支持继承;
同名interface会自动合并,同名type则会报错;
interface中可以使用this关键字,type不行;
type可以扩展原始数据类型,interface不行;
interface无法表达某些复杂类型(联合类型和交叉类型),但是type可以
官方推荐用 interface,其他无法满足需求的情况下用 type。
泛型及高级用法
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
泛型的本质是为了参数化类型、即不创建新类型,通过泛型就可以控制 函数/类/接口 接收形参时的具体类型。所以总结就是支持动态类型、以及可以实现类型约束。
参数化类型
可以考虑以下2 个需求如何实现:
- 我们需要打印或获取某变量参数类型;
- 我们已有一个接口定义,现在只是期望把所有属性变成可选;
需要怎么实现呢?需求 1 暴力做法是把能想到的参数类型都枚举列出、显然麻烦一些、且不够灵活,因为我们并不期望预置固定类型、反而希望它能灵活处理、在调用时做限制:
// 暴力写法
function print(arg: string | number | boolean): string | number | boolean {
console.log(typeof arg);
return arg;
}
// 泛型解法
function print<T>(arg: T): T {
console.log(typeof arg);
return arg;
}
需求 2 暴力写法就是重新声明一份接口、加上可选修饰符?:
// 暴力写法
interface IUser {
name: string;
age: number;
sex: string;
}
interface IUserPartial {
name?: string;
age?: number;
sex?: string;
}
const user: IUser = { name: 'cat' }; // Error: Type '{ name: string; }' is missing the following properties from type 'IUser': age, sexts(2739)
const user: IUserPartial = { name: 'cat' }; // ok
// 泛型
type Partial<T> = {
[P in keyof T]?: T[P];
};
const user: Partial<IUser> = { name: 'cat' }; // ok
再比如实现数组/元组项交换例子:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap(['cat', 'dog']); // ['dog', 'cat']
swap([3, 'dog']); // ['dog', 3]
类型约束
另外、可以利用泛型约束更精准控制函数调用参数,更可预期、有助于提高代码质量和可维护性,比如我们实现对象merge 功能:
function merge<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
const obj = { a: 1, b: 2, c: 3, d: 4 };
merge(obj, { b: 10, d: 20 }); // { a: 1, b: 10, c: 3, d: 20 }
merge(obj, { b: 10, e: 20 }); // Error: Property 'e' is missing in type '{ a: number; b: number; c: number; d: number; }' but required in type '{ b: number; e: number; }'.ts(2345)
另外关于类型约束常见的场景就是对接 API 实现数据请求功能,我们通常期望对 API 格式、字段值、请求库返回结构等做好约定、提高程序稳定性、以及开发效率:
// src/api/type.ts
// API 定义接口,可以是具体的 url,可选、可用于做请求 url拦截
interface IApi {
'/app/list': any;
'/app/detail': any;
[key: string]: Record<string, unknown>;
}
// API 返回格式 接口
interface IResponseData<T> {
code: 0 | 1 | 2 | 3;
data: T;
message: string;
}
// 具体数据格式接口
interface IUserData {
name: string;
age: number;
}
// 通用请求 类型定义
type TCommonRequest<T, U> = (url: Extract<keyof T, string>, params: Record<string, unknown>) => Promise<U | undefined>;
// 业务请求 类型定义
type TRequest<T> = TCommonRequest<IApi, IResponseData<T>>;
// src/api/index.ts
// 定义 API 常量
const Api = {
app: {
list: '/app/list',
detail: '/app/detail',
},
};
// src/components/User.tsx
// 业务组件实现 数据请求功能代码
const request: TRequest<IUserData> = async (url, params) => {
const res = await fetch(url, params);
return res.json();
};
async function fetchData() {
const user = await request(Api.app.list, {});
// outcome log
// {
// code: 0,
// result: { name: 'cat', age: 3 },
// message: '请求成功'
// }
console.log(user);
}
fetchData();
联合类型
联合类型的常用场景之一是通过多个对象类型的联合,实现手动的互斥属性,即这一属性如果有字段1,那就没有字段2:
interface IUser {
info:
| {
vip: true;
expires: string;
}
| {
vip: false;
promotion: string;
};
}
declare let user:IUser;
if (user.info.vip) {
console.log(user.info.expires); // ok
console.log(user.info.promotion); // Error: Property 'promotion' does not exist on type '{ vip: true; expires: string; }'.ts(2339)
}
关键字
is
is 类型保护,用于判断类型的函数中做类型限制
// bad
function isString(value: unknown): boolean{
return typeof value === "string";
}
//good
function isString(value: unknown): value is string{
return typeof value === "string";
}
in
in 其实就像是遍历一样
type Keys = 'a' | 'b' | 'c';
type obj = {
[ T in Keys]: string;
}
// in 遍历 Keys,并为每个值赋予 string 类型
// type Obj = {
// a: string,
// b: string,
// c: string
// }
keyof
keyof 可以获取一个对象接口的所有 key值
type obj = { a: string; b: string }
type Foo = keyof obj;
// type Foo = 'a' | 'b';
typeof
typeof 用于获取某个变量的具体类型
const obj = { a: '1' };
type Foo = typeof obj;
// type Foo = { a: string }
extends、implements
- extends用于接口与接口、类与类、接口与类之间的继承
- implements用于类与类、类与接口之间的实现
**注意: ** extends类似于es6的extends,implements没有继承效果的,但是要求子类上必须需有父类的属性和方法,更倾向于限制子类的结构!
infer
infer用于提取属性,具体的返回类型是依据三元表达式的返回而定。
type myInter<T> = T extends Array<infer U> ? U : T
Pick
用于在定义好的类型中取出特性的类型
interface UserInfo {
id: string;
name: string;
}
type NewUserInfo = Pick<UserInfo, 'name'>; // {name: string;}
Omit
用于在定义好的类型中去除特性的类型
interface UserInfo {
id: string;
name: string;
}
type NewUserInfo = Omit<UserInfo, 'name'>; // {id: string;}
Record
Record 可以获得根据 K 中所有可能值来设置 key 以及 value 的类型
interface UserInfo {
id: string;
name: string;
}
type CurRecord = Record<'a' | 'b' | 'c', UserInfo>; // { a: UserInfo; b: UserInfo; c: UserInfo; }
Partial、DeepPartial、Required
-
Partial 功能是将类型的属性变成可选
interface UserInfo { id: string name: string } // bad const machinist: UserInfo = { name: 'machinist' } // error 类型 "{ name: string; }" 中缺少属性 "id",但类型 "UserInfo" 中需要该属性。ts(2741) type NewUserInfo = Partial<UserInfo>; // good const machinist: UserInfo = { name: 'machinist' }**注意: ** Partial只支持处理第一层的属性,如果想要处理多层,可以使用
DeepPartial,使用方法与Partial相同这里就不举例了
Partial的源码,非常简单,自己就可以实现一个简易版
type Partial<T> = {
[P in keyof T]?: T[P];
};
Required (必选的)
- Required 功能与
Partial相反,将类型的属性变成必选
interface UserInfo {
id?: string
name?: string
}
type newUserInfo = Required<UserInfo>
const machinist: newUserInfo = {
id:"111"
}
// error 类型 "{ id: string; }" 中缺少属性 "name",但类型 "Required<UserInfo>" 中需要该属性。ts(2741)
Readonly (转化只读)
Readonly 就是为类型对象每一项添加前缀 Readonly
interface Person {
readonly name: string; // 只有这一项有readonly
age: number;
id?: number;
}
// 使用方法
const newObj: Readonly<Person> = {
name: '张三',
age: 1,
id: 1
};
// newObj.name = '李四'; // 异常 因为有readonly只读属性,只能初始化声明,不能赋值。
// Readonly<Person> 等同于 NewPerson
interface NewPerson {
readonly name: string;
readonly age: number;
readonly id?: number;
}
Readonly的源码实现也非常简单
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
ReturnType
ReturnType 用来获取函数的返回值的类型
type Func = (value: number) => string;
const foo: ReturnType<Func> = "1";
TS中的内置条件类型:ReturnType
先说一下条件类型是什么
- 条件类型是高级类型的一种。
- 条件类型是一种由条件表达式所决定的类型。
- 条件类型使类型具有了不唯一性,同样增加了语言的灵活性。
总言之,条件类型就是在类型中添加条件分支,以支持更加灵活的泛型,满足更多的使用场景。
例如:
T extends U ? X : Y
表示若类型T可被赋值给类型U,那么结果类型就是X类型,否则就是Y类型。
而内置条件类型则是TS内部封装好的一些类型处理,使用起来更加便利。
内置条件类型:ReturnType
在 2.8 版本中,TypeScript 内置了一些与 infer 有关的映射类型,就比如说我们今天的主角:ReturnType<Type>
其用于提取函数的返回值类型:
Constructs a type consisting of the return type of function
Type.
手撕示例:
type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any;
ReturnType<T> 只是将 infer P 从参数位置移动到返回值位置,因此此时 P 即是表示待推断的返回值类型。
// 比如
type Func = () => User;
type Test = ReturnType<Func>; // Test = User
// 其他例子
type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<<T>() => T>; // unknown
type T2 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
非法的例子:
type T = ReturnType<string>;
// Type 'string' does not satisfy the constraint '(...args: any) => any'.
type T = ReturnType<Function>;
// Type 'Function' does not satisfy the constraint '(...args: any) => any'.
// Type 'Function' provides no match for the signature '(...args: any): any'.
以上均不满足(...args: any): any'.,type T 将被视为any处理。
其他内置的条件类型还有:
Exclude<T, U> -- 从T中剔除可以赋值给U的类型。
Extract<T, U> -- 提取T中可以赋值给U的类型。
NonNullable<T> -- 从T中剔除null和undefined。
InstanceType<T> -- 获取构造函数类型的实例类型。
讲回infer
infer 最早出现在此 PR 中,表示在 extends 条件语句中待推断的类型变量。
示例如下:
type ParamType<T> = T extends (arg: infer P) => any ? P : T;
在这个条件语句 T extends (arg: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。
整句表示为:如果 T 能赋值给 (arg: infer P) => any,则结果是 (arg: infer P) => any 类型中的参数 P,否则返回为 T。
interface User {
name: string;
age: number;
}
type Func = (user: User) => void;
type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string