- 泛型(Generics)是指在定义函数、接口、类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
- 泛型
T作用域只限于函数内部使用
泛型函数
为什么需要泛型?
实现一个函数createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值。
function createArray (length:number, value:any):Array<any>{let result:Array<any> = [];for (let i=0; i<length; i++){result[i] = value;//如果像下面这样子也不会报错,但就不是 想要的那样子,每一项填充value值了。// 那么,如何约束? 这就需要泛型了。result[i] = i;}return result;}console.log(createArray(3, 'x')); // [ 'x', 'x', 'x' ]
使用泛型改造
// 第一个"<T>" 是定义一种 "T" 类型,T 在这里是不确定的类型。// 后边的T,就是表示:用前边定义的 T类型 去约束相应变量。function createArray<T>(count:number, value:T):T[]{let result:T[] = [];for (let i=0; i<count; i++){result.push(value);// result[i] = i; //报错:数组的值类型必须是string,不能将number类型的值分配给数组}return result;}// 调用的时候,才会确定 T 的具体类型// 这里传入了 string类型,所以上面的 T类型 就是 string类型;// 这里如果传入了 number类型,上面的 T类型 就是 number类型;let p1 = createArray<number>(5, 1);let p2 = createArray<string>(5, 'a');console.log(p1); //[1, 1, 1, 1, 1]console.log(p2); //["a", "a", "a", "a", "a"]
interface ICreateArray {<T>(count:number, value:T):T[],}// type ICreateArray = <T>(count:number, value:T) => T[];const createArray:ICreateArray = <T>(count:number, value:T):T[] => {let result:T[] = [];for (let i=0; i<count; i++){result.push(value);}return result;}console.log(createArray(5, 1)); //[1, 1, 1, 1, 1]console.log(createArray(3, 'a')); //["a", "a", "a"]
类数组
类数组(Array-like Object)不是数组类型,比如 arguments
function sum(){let args:IArguments = arguments;for (let i=0; i<args.length; i++){console.log(args[i]);}// for (let k of args){// console.log(args[k]);// }}sum(1,2,3); //现在已经不支持了。报错:应有0个参数,实际有3个let root = document.getElementById('root');let children: HTMLCollection = (root as HTMLElement).children;console.log(children.length);let nodeList: NodeList = (root as HTMLElement).childNodes;console.log(nodeList.length);
泛型类
class MyArray<T>{public arr: T[] = [];add(val:T){this.arr.push(val);}getMax():T{return this.arr.reduce((x,y) => x > y ? x : y);}}let arr = new MyArray<number>(); //泛型传一个 number 类型进去arr.add(1);arr.add(2);arr.add(3);console.log(arr.getMax());
如果不传泛型的话,会默认为 unknown 类型

正常传入的类,其类型会被推导成 (typeof Person)
Person默认指类实例的类型;type Person指类的类型。所以ts中想用类的类型,是无法直接用的,必须通过{new():Person}来实现。

参数是类时,尖括号中默认为类实例的类型,而不是类的类型。
// 类的类型interface IClazz<T>{new(name:string):T}// 函数的参数是一个类,返回值是该类的实例// function createClass<T>(clazz: {new(name:string):T}):T{function createClass<T>(clazz: IClazz<T>):T{return new clazz('jack');}class Person {constructor(public name:string){this.name = name;}eat(){}}// 尖括号中的类型 Person 默认指的是 Person 类实例的类型,而不是 Person 类的类型。let p1 = createClass<Person>(Person);console.log(p1);
泛型接口
泛型放在函数前,表示使用函数的时候确定了类型
interface Calculate{<T>(x:T, y:T):T; //一个函数,参数x,y类型都是T,函数返回值类型也是T}let sum:Calculate = function<T>(x:T, y:T):T{// return x+y; //报错:运算符“+”不能应用于类型“T”和“T”。return x;}console.log(sum<number>(1,2)); //1
泛型放在接口后面,表示在使用接口的时候确定类型
interface Calculate<T>{(x:T, y:T):T;}let sum:Calculate<number> = function(x:number, y:number):number{return x+y;}console.log(sum(1,2)); //3
namespace c{interface CalCulate<T>{<U>(x:T, y:T):U;}let sum:CalCulate<number> = function<U>(x:number, y:number):U{return ((x as any) + (y as any)).toString();}console.log(sum<string>(1,2)); //"3"}
多个类型参数
泛型可以有多个
// 元祖交换function swap<A,B>(tuple:[A,B]):[B,A]{return [tuple[1], tuple[0]];}let s = swap<string,number>(['a',1]);console.log(s); //[ 1, 'a' ]
默认泛型类型
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); //['x', 'x', 'x']
interface ISchool<T=number>{name:T}type StringSchool = ISchool<string>;// 不传泛型时,如果有默认类型,就是取默认类型;如果没有,就是 unknown 类型。type NumberSchool = ISchool; //等同于 type NumberSchool = ISchool<number>;let s1:StringSchool = {name: '1'};let s2:NumberSchool = {name: 1};
泛型约束
在函数中使用泛型的时候,由于预先并不知道泛型的类型,所以不能随意访问相应类型的属性和方法。
示例
namespace a{const sum = <T>(a:T, b:T):T => {return a + b; //报错:运算符 “+” 不能用于 类型 “T” 和 “T”// return (a + b) as T; //即使加上断言也是不行的}console.log(sum(1, 2));}namespace b{// 为泛型 T 加上约束(T 必须是 number 的子类型)const sum = <T extends number>(a:T, b:T):T => {// return a + b; //报错:不能将类型“number”分配给类型“T”return (a + b) as T; //当然,约束后,还是要加上断言的}console.log(sum(1, 2)); //3}
示例
function logger<T>(val:T){// console.log(val.length); //直接访问会报错}// 可以让泛型继承一个接口interface LengthWise {length:number}//约束泛型 T 必须是 LengthWise 的子类型function logger2<T extends LengthWise>(val:T){console.log(val.length);}logger2('jack'); //4// logger2(1);// 报错:number类型的参数没有length属性,所以类型 “number” 的参数不能赋给类型 “LengthWise” 的参数let obj = {length: 10};type Obj = typeof obj;logger2<Obj>(obj); //10
示例3
const getVal = <T extends object, K extends keyof T>(obj:T, key:K) => {return obj[key];}console.log(getVal({x: 1, y: 2}, 'x')); //1console.log(getVal({x: 1, y: 2}, 'name'));//报错:类型 “name” 的参数不能赋给类型 “'x' | 'y'” 的参数
示例4
namespace a{// 判断兼不兼容跟extends继承没有一点关系,只看形状 有没有对应的属性class GrandFather{}class Father extends GrandFather{}class Child extends Father{}function get<T extends Father>(){}get<GrandFather>();get<Father>();get<Child>();}namespace b{class GrandFather{grandFather:string = '1';}class Father extends GrandFather{father:string = '2';}class Child extends Father{child:string = '3';}//约束,要满足:T的实例能赋值给Father的实例//也可以认为是 T是Father的子类型function get<T extends Father>(){}get<Father>();get<Child>();// get<GrandFather>(); //报错了:// 类型 "GrandFather" 不满足约束// 类型 "GrandFather" 中缺少属性 "father" ,但类型 "Father" 中需要该属性//子类的实例可以赋值给父类的实例;但是不能反过来赋值。let father = new Father();let child = new Child();father = child;// child = father; //报错:类型 "Father" 中缺少属性 "child" ,但类型 "Child" 中需要该属性。}
compose
//声明一个 Func 类型的函数type Func<T extends any[], R> = (...a: T) => R/*** Composes single-argument functions from right to left. The rightmost* function can take multiple arguments as it provides the signature for the* resulting composite function.** @param funcs The functions to compose.* @returns A function obtained by composing the argument functions from right* to left. For example, `compose(f, g, h)` is identical to doing* `(...args) => f(g(h(...args)))`.*//* zero functions */export default function compose(): <R>(a: R) => R/* one functions */export default function compose<F extends Function>(f: F): F/* two functions */export default function compose<A, T extends any[], R>(f1: (a: A) => R,f2: Func<T, A>): Func<T, R>/* three functions */export default function compose<A, B, T extends any[], R>(f1: (b: B) => R,f2: (a: A) => B,f3: Func<T, A>): Func<T, R>/* four functions */export default function compose<A, B, C, T extends any[], R>(f1: (c: C) => R,f2: (b: B) => C,f3: (a: A) => B,f4: Func<T, A>): Func<T, R>/* rest */export default function compose<R>(f1: (a: any) => R,...funcs: Function[]): (...args: any[]) => Rexport default function compose<R>(...funcs: Function[]): (...args: any[]) => Rexport default function compose(...funcs: Function[]) {if (funcs.length === 0) {// infer the argument type so it is usable in inference down the linereturn <T>(arg: T) => arg}if (funcs.length === 1) {return funcs[0]}//从右向左开始计算return funcs.reduce((a, b) => (...args: any) => a(b(...args)))}//compose(fn1, fn2, fn3)(1);// 等同于 fn1(fn2(fn3(1)));
解析
const add1 = (x:string) => x + "1";const add2 = (x:string) => x + "2";const add3 = (x:string) => x + "3";// export default function compose(): <R>(a: R) => Rconsole.log(compose()('jack')); //"jack" R为 string 类型// export default function compose<F extends Function>(f: F): Fconsole.log(compose(add1)('jack')); //"jack1"// f 就是 add1。 F 是Function的子类型/**type Func<T extends any[], R> = (...a: T) => Rexport default function compose<A, T extends any[], R>(f1: (a: A) => R, //f1就是add1f2: Func<T, A> //f2就是add2): Func<T, R>*/console.log(compose<string, any[], string>(add1, add2)('jack')); //"jack21"// Func<T, R> T=["jack"] R="jack21"// compose() 返回一个函数,假设为fn,// fn参数的类型是 T(any[]),fn返回值的类型是 R(这里R是string)// f2: Func<T, A> T=["jack"] A="jack2"// f2参数的类型是 T(any[]);f2返回值的类型是 A,f2 的返回值就是f1的参数// f1: (a: A) => R, A="jack2" R="jack21"// f1参数的类型 是 A;f1返回值的类型是 R
泛型类名别名
泛型类型别名可以表发更复杂的类型。
type Cart<T> = {list:Array<T>} | Array<T>;let c1:Cart<string> = {list: ['1', '2']}let c2:Cart<number> = [1,2,3]
泛型接口 vs 泛型类型别名
- 接口创建了一个新的名字,它可以在其它任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名
- 类型别名不能被 extends 和 implements,这时我们应该尽量使用接口代替类型别名
- 当我们需要使用联合类型或者元祖类型的时候,类型别名会更合适
