TypeScript
软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性:
比如我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作;
但是对于参数的类型是否也可以参数化呢?
什么是类型的参数化?
我们来提一个需求:封装一个函数,传入一个参数,并且返回这个参数;
如果我们是TypeScript的思维方式,要考虑这个参数和返回值的类型需要一致:
function foo(arg: number):number {
return arg
}
上面的代码虽然实现了,但是不适用于其他类型,比如string、boolean、Person等类型:
我么可以使用any来解决这个问题
function foo(arg: any):any {
return arg
}
虽然any是可以的,但是定义为any的时候,我们其实已经丢失了类型信息:
比如我们传入的是一个number,那么我们希望返回的可不是any类型,而是number类型;
所以,我们需要在函数中可以捕获到参数的类型是number,并且同时使用它来作为返回值的类型;
我们需要在这里使用一种特性的变量 - 类型变量(type variable),它作用于类型,而不是值:
function foo<Type>(arg: Type): Type{
return arg
}
这里我们可以使用两种方式来调用它:
方式一:通过 <类型> 的方式将类型传递给函数;
方式二:通过类型推导(type argument inference),自动推到出我们传入变量的类型:
在这里会推导出它们是 字面量类型的,因为字面量类型对于我们的函数也是适用的
foo<string>('abc')
foo<number>(123)
foo('bcd')
foo(234)
泛型的基本补充
当然我们也可以传入多个类型
function foo<T, E>(al: T, a2: E) {
}
平时在开发中我们可能会看到一些常用的名称:
T:Type的缩写,类型
K、V:key和value的缩写,键值对
E:Element的缩写,元素
O:Object的缩写,对象
泛型接口
在定义接口的时候我们也可以使用泛型
interface IFoo<T> {
initialValue: T,
valueList: T[],
handleValue: (value: T) => void
}
fonst foo: IFoo<number> = {
initialValue: 0,
valueList: [0, 2, 3],
handleValue: function(value: number) {
console.log(value)
}
}
泛型类
我们也可以编写一个泛型类:
class Point<T, K> {
x: T;
y: K;
constructor(x: T, y: K) {
this.x = x;
this.y = y;
}
}
const p1 = new Point(10, 20);
const p2 = new Point<string, string>('123', '234');
console.log(p1.x);
console.log(p2.x);
泛型约束(Generic Constraints)
有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中:
比如string和array都是有length的,或者某些对象也是会有length属性的;
那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢?
interface ILength {
length: number;
}
//T相当于是一个变量 用于记录本次调用的类型 所以在整个函数的执行周期中 一直保留着参数类型
function getInfo<T extends ILength>(args: T): T {
return args;
}
const info1 = getInfo('aaaa');
const info2 = getInfo(['aaa', 'bbb', 'ccc']);
const info3 = getInfo({ length: 100 });
这里表示是传入的类型必须有这个属性,也可以有其他属性,但是必须至少有这个成员。
在泛型约束中使用类型参数(Using Type Parameters in Generic Constraints)
你可以声明一个类型参数,这个类型参数被其他类型参数约束;
举个栗子:我们希望获取一个对象给定属性名的值
我们需要确保我们不会获取 obj 上不存在的属性;
所以我们在两个类型之间建立一个约束;
//传入的key的类型 obj当中key的其中之一
interface IPerson{
name: string;
age: number;
}
type IPersonKeys = keyof IPerson; //相当于 "name" | "age" key的联合类型
// function getObjectProperty<O, K extends keyof 'name' | 'age' | 'height'>(obj: O, key: K) {
// return obj[key];
// }
function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {
return obj[key];
}
const info = {
name: 'kobe',
age: 18,
height: 1.88,
};
const name = getObjectProperty(info, 'name');
映射类型(Mapped Types)
有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型。
大部分内置的工具都是通过映射类型来实现的;
大多数类型体操的题目也是通过映射类型完成的;
映射类型建立在索引签名的语法上:
映射类型,就是使用了 PropertyKeys 联合类型的泛型;
其中 PropertyKeys 多是通过 keyof 创建,然后循环遍历键名创建一个类型;
ts提供了映射类型: 函数
映射类型不能使用interface定义
type MapPerson<Type> = {
// 索引类型依次进行使用
[Property in keyof Type]: Type[Property];
};
interface IPerson {
name: string;
age: number;
}
type NewPerson = MapPerson<IPerson>;
映射修饰符(Mapping Modifiers)
在使用映射类型时,有两个额外的修饰符可能会用到:
一个是 readonly,用于设置属性只读;
一个是 ? ,用于设置属性可选;
你可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀。
type MapIPerson<Type> = {
readonly [Property in keyof Type]?: Type[Property];
};
interface IPerson {
name: string;
age: number;
height: number;
address: string;
}
type IPersonOptional = MapIPerson<IPerson>;
const p: IPersonOptional = {};
type MapIPerson<Type> = {
-readonly [Property in keyof Type]-?: Type[Property];
};
interface IPerson {
name: string;
age?: number;
readonly height: number;
address: string;
}
type IPersonOptional = MapIPerson<IPerson>;
内置工具与类型体操
类型系统其实在很多语言里面都是有的,比如Java、Swift、C++等等,但是相对来说TypeScript的类型非常灵活:
这是因为TypeScript的目的是为JavaScript添加一套类型校验系统,因为JavaScript本身的灵活性,也让TypeScript类型系统
不得不增加更附加的功能以适配JavaScript的灵活性;
所以TypeScript是一种可以支持类型编程的类型系统;
这种类型编程系统为TypeScript增加了很大的灵活度,同时也增加了它的难度:
如果你不仅仅在开发业务的时候为自己的JavaScript代码增加上类型约束,那么基本不需要太多的类型编程能力;
但是如果你在开发一些框架、库,或者通用性的工具,为了考虑各种适配的情况,就需要使用类型编程;
TypeScript本身为我们提供了类型工具,帮助我们辅助进行类型转换(前面有用过关于this的类型工具)。
条件类型(Conditional Types)
很多时候,日常开发中我们需要基于输入的值来决定输出的值,同样我们也需要基于输入的值的类型来决定输出的值的类型。
条件类型(Conditional types)就是用来帮助我们描述输入类型和输出类型之间的关系。
条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression ):
SomeType extends OtherType ? TrueType : FalseType;
function sum<T extends number | string>(num1: T, num2: T): T extends number ? number : string;
function sum(num1, num2) {
return num1 + num2;
}
const res = sum(20, 30);
const res2 = sum('abc', 'cba');
在条件类型中推断(inter)
在条件类型中推断(Inferring Within Conditional Types)
条件类型提供了 infer 关键词,可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果;
比如我们现在有一个数组类型,想要获取到一个函数的参数类型和返回值类型:
type CalcFnType = (num1: number, num2: number) => number;
function foo() {
return 'abc';
}
//同届类型体操的题目: MyReturnType
//推断返回值类型
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R
? R
: false;
//推断参数类型
type MyParameterType<T extends (...args: any[]) => any> = T extends (...args: infer A) => any
? A
: never;
//获取一个函数的返回值类型
type CalcReturnType = MyReturnType<CalcFnType>;
type FooReturnType = MyReturnType<typeof foo>;
type FooReturnType1 = ReturnType<CalcFnType>;
type CalcParameterType = MyParameterType<CalcFnType>;
分发条件类型(Distributive Conditional Types)
在泛型中使用条件类型的时候 如果传入一个联合类型 就会变成分发的
// type toArray<T> = T[]; //(string | number)[]
type toArray<T> = T extends any ? T[] : never; //number[] | string[]
type NumArray = toArray<number>;
//number[] | string[] 而不是(number | string)[]
type NumAndStrArray = toArray<number | string>;
如果我们在 ToArray 传入一个联合类型,这个条件类型会被应用到联合类型的每个成员:
当传入string | number时,会遍历联合类型中的每一个成员;
相当于ToArray | ToArray;
所以最后的结果是:string[] | number[];
Partial
用于构造一个Type下面的所有属性都设置为可选的类型
interface IPerson {
name: string;
age: number;
slogan?: string;
}
type IPersonOption2 = Partial<IPerson>;
实现myPartial
type MyPartial<T> = {
[p in keyof T]?: T[p];
};
//IKun类型都是可选的
type IPersonOption1 = MyPartial<IPerson>;
Required
用于构造一个Type下面的所有属性全都设置为必填的类型,这个工具类型跟 Partial 相反。
interface IPerson {
name: string;
age: number;
slogan?: string;
}
type IKunOption1 = Required<IPerson>;
实现MyRequired
type MyRequired<T> = {
[p in keyof T]-?: T[p];
};
type IKunOption2 = MyRequired<IPerson>;
Readonly
用于构造一个Type下面的所有属性全都设置为只读的类型,意味着这个类型的所有的属性全都不可以重新赋值。
interface IPerson {
name: string;
age: number;
slogan?: string;
}
type MyReadonly<T> = {
readonly [p in keyof T]: T[p];
};
type IPersonOption1 = Readonly<IPerson>;;
实现MyReadonly
type MyReadonly<T> = {
readonly [p in keyof T]: T[p];
};
type IPersonOption2 = MyReadonly<IPerson>;
Record<Keys, Type>
用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型。
interface IPerson {
name: string;
age: number;
slogan?: string;
}
type t1 = '上海' | '北京' | '洛杉矶';
type IPersons= Record<t1, IPerson>;
const ipersons:IPersons = {
上海: {
name: 'xxx',
age: 10,
},
北京: {
name: 'yyy',
age: 5,
},
洛杉矶: {
name: 'zzz',
age: 3,
},
};
实现MyRecord
//确保keys一定是可以作为key的联合类型 Keys extends keyof any
type keys = keyof IPerson; // name | age | slogan
type Res = keyof any; // =>number | string | symbol
type MyRecord<Keys extends keyof any, T> = {
[P in Keys]: T;
};
type t1 = '上海' | '北京' | '洛杉矶';
type IPersons = MyRecord<t1, IPerson>;
Pick<Type, Keys>
用于构造一个类型,它是从Type类型里面挑了一些属性Keys
interface IPerson {
name: string;
age: number;
slogan?: string;
}
type IPerson1 = Pick<IPerson, 'name' | 'slogan'>;
实现MyPick
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type IPerson2 = MyPick<IPerson, 'name' | 'slogan'>;
Omit<Type, Keys>
用于构造一个类型,它是从Type类型里面过滤了一些属性Keys
interface IPerson {
name: string;
age: number;
slogan?: string;
}
type IPerson1 = Omit<IPerson, 'name' | 'slogan'>;
实现MyOmit
//类型体操
type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
type IPerson2 = MyOmit<IPerson, 'name' | 'slogan'>;
```
## Exclude<UnionType, ExcludedMembers>
用于构造一个类型,它是从UnionType联合类型里面排除了所有可以赋给ExcludedMembers的类型。
```typescript
type IPerson = '唱' | '跳' | 'rap';
type IPerson1 = Exclude<IPerson, 'rap'>;
```
实现MyExclude
```typescript
type MyExclude<T, E> = T extends E ? never : T;
type IPerson2 = MyExclude<IPerson, 'rap'>;
```
有了MyExclude 我们可以使用它来实现MyOmit
```typescript
type MyOmit<T, K> = Pick<T, MyExclude<keyof T, K>>
```
## Extract<Type, Union>
用于构造一个类型,它是从Type类型里面提取了所有可以赋给Union的类型。
```typescript
type IPerson = '唱' | '跳' | 'rap';
type IPerson1 = Extract<IPerson, 'rap'>;
```
实现MyExtract
```typescript
type MyExtract<T, E> = T extends E ? T : never;
type IPerson2 = MyExtract<IPerson, 'rap'>;
```
## NonNullable<Type>
用于构造一个类型,这个类型从Type中排除了所有的null、undefined的类型。
```typescript
type IPerson = '唱' | '跳' | 'rap' | null | undefined;
type IPerson1 = NonNullable<IPerson>;
```
实现MyNonNullable
```typescript
type MyNonNullable<T> = T extends null | undefined ? never : T;
type IPerson2 = MyNonNullable<IPerson>;
```
## InstanceType<Type>
用于构造一个由所有Type的构造函数的实例类型组成的类型。
```typescript
class Person {}
class Dog {}
const p1: Person = new Person();
//type Person 构造函数具体的类型
//InstanceType 构造函数创建出来的实例对象的类型
type MyPerson = InstanceType<typeof Person>;
const p2: MyPerson = new Person();
```
此时p1和p2都是Person类型 可能会觉得instanceType没有用
但是我们可以看下面的情况
```typescript
function factory<T extends new (...args: any[]) => any>(ctor: T): T {
return new ctor();
}
const p3 = factory(Person);
const d = factory(Dog);
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/b2749c9547e14104850da1211e223fab.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/4a074c44238d4e2983457089fb93dc41.png)
此时返回的类型不是我们期望的
```typescript
function factory<T extends new (...args: any[]) => any>(ctor: T): InstanceType<T> {
return new ctor();
}
const p3 = factory(Person);
const d = factory(Dog);
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/1b6eb5b7f31b440c8b551c47500bb1b7.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/2acb0e63e8064114ab807a1e6f43b37e.png)
此时就正确了
实现MyInstanceType
```typescript
type MyInstanceType<T extends new (...args: any[]) => any> = T extends new (
...args: any[]
) => infer R
? R
: never;
```