条件类型
TypeScript 中的条件类型是一种高级类型,它使我们根据不同的条件创建更复杂的类型。
TS中的条件类型就是在类型中添加条件分支,以支持更加灵活的泛型
条件类型允许我们根据某个类型的属性是否满足某个条件,来确定最终的类型。
type IsString<T> = T extends string ? "yes" : "no";
let result4: IsString<"hello"> = "yes"; // 类型为 "no"
let result3: IsString<42> = "no"; // 类型为 "yes"
console.log(result4, result3); // yes no
// let result2: IsString<"hello"> = "no"; // 类型为 "no"
// let result1: IsString<42> = "yes"; // 类型为 "yes"
// 通过给泛型参数传递不同的类型,得到了不同的结果。
- infer 关键字
infer 关键字用于引入一种类型变量,定义泛型里面推断出来的类型参数,而不是外部传入的类型参数。
通常,infer 关键字结合条件类型使用,用于提取和推断类型信息。
// infer R 表示 R 类型是 TypeScript 自己推断出来的,不用显式传入。
// 传入的 T 是函数, 就返回这个函数的结果类型。
type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function sum(a: string, b: string): string {
return a + b;
}
const result1: ExtractReturnType<typeof sum> = 'hello'; // 编译通过
console.log(result1) // hello
- tuple转union 元组转为联合类型
type ElementOf<T> = T extends Array<infer P> ? P : never;
type Tuple = [string, number];
type TupleToUnion = ElementOf<Tuple>;
// 等同于
// type TupleToUnion = string | number
let arr: TupleToUnion = '迪西'
let arr2: TupleToUnion = 9
- 联合类型转成交叉类型 string | number =》string & number
type T1 = { name: string };
type T2 = { age: number };
type ToIntersection<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
// 由于U需要同时满足T1的定义、T2的定义,因此U需要包含T1、T2所有的类型,因此T3就是T1 & T2
type T3 = ToIntersection<{ a: (x: T1) => void, b: (x: T2) => void }>; // type T3 = T1 & T2
let obj: T3 = {
name: '迪西',
age: 9,
}
- 条件类型中的泛型推断
条件类型中的泛型推断是一个重要的概念,// 条件类型中的泛型推断 type ExtractType<T> = T extends infer U ? U : never; // 使用泛型推断的条件类型 type ExtractedType = ExtractType<string[]>; // 查看结果类型 let extractedValue: ExtractedType = ["1", "2"];
- 分布式条件类型
条件类型在处理联合类型时表现出一种分布式的行为,type Diff<T, U> = T extends U ? never : T; // 返回T 不属于U的子类型的部分 type Filter<T, U> = T extends U ? T : never; // 返回T 属于U的子类型的部分 type OnlyStrings = Diff<"a" | "b" | 1 | 2 | false, number | boolean>; // "a" | "b" | false type OnlyNumbers = Filter<"a" | "b" | 1 | 2 | true, number | boolean>; // 1 | 2 | true let str1: OnlyStrings = 'a' let str2: OnlyNumbers = 1
- 内置条件类型
在 TypeScript 中,有一些内置的条件类型,比如 NonNullable,它从类型中排除了 null 和 undefined。这里确保 value 的类型是非空的字符串。type NonNullableType<T> = T extends null | undefined ? never : T; const value1: NonNullableType<string | null> = '迪西'; // 编译通过 // const value2: NonNullableType<string | null> = null; // 编译通过
- 分配条件类型
分布式条件类型是指在联合类型上进行条件类型判断时,该判断会分布到联合类型的每个成员上,从而生成新的联合类型。type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : any; // MyReturnType ,应用于函数类型和非函数类型。由于条件类型的分布式能力,它会分别应用于函数类型和非函数类型的每个成员,最终生成新的联合类型 function sum(a: number):number { return a; } // 编译通过 nonFuncResult 类型会被推断为string const nonFuncResult: MyReturnType<string> = "Hello"; // 编译通过 funcResult 类型会被推断为number const funcResult: MyReturnType<typeof sum> = 3;
- 条件类型在映射类型中的应用
映射类型是一种用于从现有类型创建新类型的工具,可以根据对象的属性是否满足条件来创建一个新类型,从而对现有类型的属性进行转换、修改或过滤。type MakeNullable<T> = { [K in keyof T]: T[K] extends number ? T[K] | null : T[K]; }; // 使用映射类型中的条件类型 type User = { id: number; name: string; age: number; }; type NullableUser = MakeNullable<User>; // 将 T 中的 number 类型属性改为可空。通过应用于 User 类型,我们得到了一个可空的 NullableUser 类型。 // 查看结果类型 let nullableUser: NullableUser = { id: 1, name: "John", age: null, };
- 条件类型中的嵌套应用
条件类型可以嵌套使用,形成更复杂的类型判断。// 嵌套应用的条件类型 type FlattenArray<T> = T extends Array<infer U> ? FlattenArray<U> : T; // 使用嵌套应用的条件类型 type NestedArray = ['迪西', ['小波', ['拉拉', '嘻嘻'], '哈哈']]; type FlatArray = FlattenArray<NestedArray>; // 查看结果类型 let flatArray: FlatArray = '迪西';
函数
- 函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。
- 和JavaScript一样,TypeScript函数可以创建有名字的函数和匿名函数。可以随意选择适合应用程序的方式,不论是定义一系列API函数还是只使用一次的函数。
// Named function
function add(x, y) {
return x + y;
}
// 匿名函数 函数表达式是一种将函数赋值给变量的方式
let myAdd = function(x, y) { return x + y; };
- 函数类型
函数类型包含2部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的
- 参数类型
为每个参数指定一个名字和类型。 这个名字只是为了增加可读性。
只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否正确。let myAdd: (baseValue: number, increment: number) => number = function(x: number, y: number): number { return x + y; };
- 返回值类型
返回值,我们在函数和返回值类型之前使用( =>)符号,使之清晰明了。
返回值类型是函数类型的必要部分,如果函数没有返回任何值,你也必须指定返回值类型为 void而不能留空。let myAdd = function(x: number, y: number): number { return x + y; };
- 可选参数和默认参数
使用?符号来定义可选参数。可选参数可以不传递
在调用这些函数时,我们可以选择传递或不传递可选参数。// 1).可选参数 ?代表参数可选,age可传可不传 function func1(name: string, age?: number): void { console.log(name); // 张三 } func1('张三'); // 2).可选参数,参数有默认值 function func2(url: string, method: string = 'POST'): void { console.log(url, method); // /list POST } func2('/list');
- 剩余参数
可以使用…符号来定义剩余参数。剩余参数可以接受任意数量的参数,并将它们作为数组传递给函数function func1(...arge: number[]): number { return arge.reduce((val, item) => val + item, 0); } console.log(func1(4, 5, 6, 7)); // 22
- 函数重载
函数重载是一种在 TypeScript 中定义多个具有相同名称但参数类型和数量不同的函数的技术。这可以编写更清晰和类型安全的代码。
let obj: any = {};
function func1(val: string): void;
function func1(val: number): void;
function func1(val: boolean): void;
function func1(val: any): void {
if (typeof val === 'string') {
obj.name = val;
} else if (typeof val === 'number') {
obj.age = val;
} else if (typeof val === 'boolean' ) {
obj.isSuccess = val;
}
}
func1('迪西');
func1(10);
func1(true);
console.log(obj); // {name: '迪西', age: 10, isSuccess: true}
装饰器
要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用experimentalDecorators编译器选项:
tsc --target ES5 --experimentalDecorators
tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
解决bug
编译ts文件报错:Unable to resolve signature of method decorator when called as an expression.
官网的配置还是不能解决报错
可以通过监ts文件的热更新。
$ tsc 文件名 --target ES5 -w --experimentalDecorators
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
装饰器的作用
1)只能在类中使用
2)减少冗余代码量
3)提高代码扩展性
- 装饰器的语法
装饰器本质上就是一个函数,在特定的位置调用装饰器函数即可对数据进行扩展。// target 表示要扩展的数据,可以是类、方法、属性、参数等。 function myDecorator(target: any) { // 对 target 进行处理 }
- 类装饰器
类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 类装饰器不能用在声明文件中( .d.ts),也不能用在任何外部上下文中
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。注意 如果你要返回一个新的构造函数,你必须注意处理好原来的原型链。 在运行时的装饰器调用逻辑中 不会为你做这些。
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) { return class extends constructor { newProperty = "new property"; hello = "override"; } } @classDecorator class Greeter { property = "property"; hello: string; constructor(m: string) { console.log(m) // world this.hello = m; } } console.log(new Greeter("world")); // class_1 {property: 'property', hello: 'override', newProperty: 'new property'}
- 装饰器工厂
若需要要定制一个修饰器如何应用到一个声明上,得写一个装饰器工厂函数。 装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。function color(value: string) { // 这是一个装饰器工厂 return function (target) { // 这是装饰器 // do something with "target" and "value"... } }
function addNameEatFactory(name: string):Function { return function addNameEat(constructor: Function):void { constructor.prototype.name = name; constructor.prototype.eat = (m) => { console.log(m) // 2 }; }; }; @addNameEatFactory('Jerry') class Person { name: string; eat: Function | undefined; constructor(n) { console.log(n) // 1 this.name = n; } } let p: Person = new Person(1); console.log(p.name); // 1 p.eat(2);
- 属性、方法装饰器
方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts),重载或者任何外部上下文(比如declare的类)中。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:1)对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2)成员的名字。
3)成员的属性描述符。
注意 如果代码输出目标版本小于ES5,属性描述符将会是undefined。
如果方法装饰器返回一个值,它会被用作方法的属性描述符。
注意 如果代码输出目标版本小于ES5返回值会被忽略。function upperCase(target: any, propertyKey: string) { // console.log(target, propertyKey); // { getName: [Function (anonymous)], sun: [Function (anonymous)] } name let value = target[propertyKey] const getter = () => value; const setter = (newVal: string) => { value = newVal.toUpperCase(); }; if (delete target[propertyKey]) { Object.defineProperty(target, propertyKey, { get: getter, set: setter, enumerable: true, configurable: true }); } } // 如果装饰的是静态属性,target就是构造函数 function staticPrototypeDecorator(target: any, propertyKey: string) { // console.log(target, propertyKey); // [Function: Person] { age: 18 } age } function noEnumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.enumerable = false; // 不可枚举 } function toNumber(target: any, propertyKey, descriptor: PropertyDescriptor) { let oldMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(args) // ['1', '2', '3', '4'] args = args.map(item => parseFloat(item)); return oldMethod.apply(this, args) } } class Person { @upperCase name: string = 'jerry'; // 实例属性 @staticPrototypeDecorator static age: number = 18; // 静态属性 @noEnumerable getName() { console.log(this.name); }; // 实例方法 @toNumber sum(...args: any[]) { // 实例方法 console.log(args) // [1, 2, 3, 4] return args.reduce((prev: number, next: number) => prev + next, 0); } } let p = new Person(); console.log(p.name); // JERRY console.log(p.sum('1', '2', '3', '4')); // 10
- 参数装饰器
参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文里。参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
1)对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2)成员的名字。
3)参数在函数参数列表中的索引。
注意 参数装饰器只能用来监视一个方法的参数是否被传入。// target:静态属性指构造函数,实例属性、实例方法值构造函数的原型 // methodName:方法的名称 // paramIndex:参数的索引 function addAge(target: any, methodName: string, paramIndex: number) { // console.log(target, methodName, paramIndex); // { login: [Function (anonymous)] } login 1 target.age1 = 18; } class Person { age1: number; login(username: string, @addAge password: string) { console.log(this.age1, username, password); // 18 admin admin123 } } let p = new Person(); p.login('admin', 'admin123'); // 18 'admin' 'admin123'
- 装饰器执行顺序
- 1)class装饰器最后执行,后写的类装饰器先执行
- 2)方法和参数中的装饰器,参数装饰器先执行,再执行方法装饰器
- 3)方法和属性装饰器,谁在前面先执行谁
- 4)先内后外 先上后下执行
function ClassDecorator1() { return function (target) { console.log('ClassDecorator1'); } } function ClassDecorator2() { return function (target) { console.log('ClassDecorator2'); } } function PropertyDecorator(name: string) { return function (target, propertyName) { console.log('PropertyDecorator', propertyName, name); } } function MethodDecorator() { return function (target, propertyName) { console.log('MethodDecorator', propertyName); } } function ParameterDecorator() { return function (target, propertyName, index) { console.log('ParameterDecorator', propertyName, index); } } @ClassDecorator1() @ClassDecorator2() class Person { @PropertyDecorator('name') name: string = ''; @PropertyDecorator('age') age: number = 9; @MethodDecorator() hello(@ParameterDecorator() hello: string, @ParameterDecorator() word: string) {} }