# 泛型
- 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
- 泛型T作用域只限于函数内部使用
# 泛型函数
- 实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值
function createArray(length: number, value: any): Array<any> {
let result: any = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
let result = createArray(3,'x');
console.log(result);
- 使用泛型
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
let result = createArray2<string>(3,'x');
console.log(result);
# 类数组
类数组(Array-like Object)不是数组类型,比如 arguments
function sum() {
let args: IArguments = arguments;
for (let i = 0; i < args.length; i++) {
console.log(args[i]);
}
}
sum(1, 2, 3);
let root = document.getElementById('root');
let children: HTMLCollection = (root as HTMLElement).children;
children.length;
let nodeList: NodeList = (root as HTMLElement).childNodes;
nodeList.length;
# 泛型类
在类中使用泛型也很简单,我们只需要在类名后面,使用 <T, ...>
的语法定义任意多个类型变量
interface GenericInterface<U> {
value: U
getIdentity: () => U
}
class IdentityClass<T> implements GenericInterface<T> {
value: T
constructor(value: T) {
this.value = value
}
getIdentity(): T {
return this.value
}
}
const myNumberClass = new IdentityClass<Number>(100);
console.log(myNumberClass.getIdentity()); // 100
const myStringClass = new IdentityClass<string>("xxx!");
console.log(myStringClass.getIdentity()); // xxx!
- 在实例化 IdentityClass 对象时,我们传入Number 类型和构造函数参数值 100;
- 之后在 IdentityClass 类中,类型变量 T 的值变成 Number 类型;
- IdentityClass 类实现了 GenericInterface
<T>
,而此时 T 表示 Number 类型,因此等价于该类实现了 GenericInterface<Number>
接口; - 而对于 GenericInterface
<U>
接口来说,类型变量 U 也变成了 Number,类型值沿链向上传播,且与变量名无关
# 泛型接口
泛型接口可以用来约束函数
interface Identities<V, M> {
value: V,
message: M
}
function identity<T, U> (value: T, message: U): Identities<T, U> {
console.log(value + ": " + typeof (value));
console.log(message + ": " + typeof (message));
let identities: Identities<T, U> = {
value,
message
};
return identities;
}
console.log(identity(100, "xxx"));
// 100: number
// xxx: string
// {value: 100, message: "xxx"}
# 默认泛型类型
function createArray3<T=number>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
let result2 = createArray3(3,'x');
console.log(result2);
# 泛型约束
# 确保属性存在
有时候,我们希望类型变量对应的类型上存在某些属性。这时,除非我们显式地将特定属性定义为类型变量,否则编译器不会知道它们的存在。 一个很好的例子是在处理字符串或数组时,我们会假设 length 属性是可用的。让我们再次使用 identity 函数并尝试输出参数的长度:
function identity<T>(arg: T): T {
console.log(arg.length); // Error
return arg;
}
在这种情况下,编译器将不会知道 T 确实含有 length 属性,尤其是在可以将任何类型赋给类型变量 T 的情况下。我们需要做的就是让类型变量 extends 一个含有我们所需属性的接口,比如这样:
interface Length {
length: number;
}
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
T extends Length用于告诉编译器,我们支持已经实现 Length 接口的任何类型。之后,当我们使用不含有 length 属性的对象作为参数调用 identity 函数时,TypeScript 会提示相关的错误信息:
identity(100); // Error
// Argument of type '100' is not assignable to parameter of type 'Length'.(2345)
# 检查对象上的键是否存在
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number
# 泛型类型别名
泛型类型别名可以表达更复杂的类型
type Cart<T> = {list:T[]} | T[];
let c1:Cart<string> = {list:['1']};
let c2:Cart<number> = [1];
# 泛型接口 vs 泛型类型别名
接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名
类型别名不能被 extends和 implements,这时我们应该尽量使用接口代替类型别名
当我们需要使用联合类型或者元组类型的时候,类型别名会更合适
# 泛型工具类型
# Partial
Partial 可以将传入的属性由非可选变为可选,具体使用如下
/**
* node_modules/typescript/lib/lib.es5.d.ts
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。
interface User {
name: string;
age: number;
flag: boolean;
}
type uPartial = Partial<A>;
const user: uPartial = {}; // 不会报错
# Record
Record<K extends keyof any, T>
的作用是将 K
中所有的属性的值转化为 T
类型。
/**
* node_modules/typescript/lib/lib.es5.d.ts
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
const x: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { title: "home" }
};
# Pick
Pick<T, K extends keyof T> 的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型
// node_modules/typescript/lib/lib.es5.d.ts
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false
};
# Exclude
Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉。
// node_modules/typescript/lib/lib.es5.d.ts
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;
// 如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T 类型。最终实现的效果就是将 T 中某些属于 U 的类型移除掉。
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
# Required
Required 可以将传入的属性中的可选项变为必选项,这里用了 -? 修饰符来实现。
/**
* type Require<T> = { [P in keyof T]-?: T[P] };
*/
interface Person{
name:string;
age:number;
gender?:'male'|'female';
}
let p:Required<Person> = {
name:'zhufeng',
age:10,
//gender:'male'
}
# Readonly
Readonly 通过为传入的属性每一项都加上 readonly 修饰符来实现。
interface Person{
name:string;
age:number;
gender?:'male'|'female';
}
//type Readonly<T> = { readonly [P in keyof T]: T[P] };
let p:Readonly<Person> = {
name:'zhufeng',
age:10,
gender:'male'
}
p.age = 11; //error
← ts接口