条件类型
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 | numberlet 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 & T2let 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 | truelet 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) // worldthis.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) // 1this.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)] } namelet 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 {@upperCasename: string = 'jerry'; // 实例属性@staticPrototypeDecoratorstatic age: number = 18; // 静态属性@noEnumerablegetName() { console.log(this.name); }; // 实例方法@toNumbersum(...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 1target.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) {} }