学 Java 的时候总会提到泛型,现在 TS 也有了,他们的用法都差不太多。泛型可以理解为广泛的类型。
为什么要用泛型
先来了解下泛型有什么用。先看下面定义的一个函数:
function itself(a: number): number { return a } itself(1) // 1
上面的函数就是简单的传入一个数,返回一个数,但是现在想传入字符串,返回字符串呢?或者再加传入数组,返回数组呢?这就需要用到泛型了。
function itself<T>(a: T):T { return a } console.log(itself(true)) // true console.log(itself(1)) // 1 console.log(itself('1')) // '1' console.log(itself([1, 2, 3])) // [1, 2, 3]
从上面的例子可以看到,T 有点像是一个临时变量一样,先推断出传入参数的类型,将这个类型赋值到 T T = XXX 类型
,后面就直接 返回的类型 = T = XXX 类型
。这么说可能不那么准确,但是可以看到 T 就是一个占位符。
如果调用函数时使用显式声明可能会更直观。
function itself<T>(a: T):T { return a } console.log(itself<boolean>(true)) // true console.log(itself<number>(1)) // 1 console.log(itself<string>('1')) // '1' console.log(itself<Array<number>>([1, 2, 3])) // [1, 2, 3
这里发现 Array<number>
里也是用了泛型的,这样可以声明数组里元素的类型。
接口与泛型
上面都是传基本类型(除了 Array),下面看看接口与泛型的配合。
interface Human { name: string age: number } interface Animal { category: string } function create<T>(what: T): T { return what } create<Human>({ name: 'Jack', age: 18 }) create<Animal>({ category: 'dog' })
用法几乎一样,Easy~。
有了接口这个东西后,我们可以玩更高级一点的。现在我想造一个 Human,规定这个 Human 一定要有 JJ,可以这么写。
interface JJ { jjSize: string jjLength: number } interface Human { name: string age: number } function create<T extends JJ>(what: T): T { return what } create({ name: 'Jack', age: 18, jjSize: 'large', jjLength: 18 }) create({ name: 'Jack', age: 18 }) // 报错:没有 jjSize 和 jjLength
上面的这个用法叫做泛型的约束,像刚刚说的这个 Human 一定要有 JJ,这就是一个约束。
类与泛型
说完接口,肯定逃不了要说类了。我们先来看一段 JS 的代码。
function create(C) { return new C() }
正确的理解应该是传入一个构造函数 C,然后返回 C 的实例,没了。但是这个函数啥约束都没有,如果传入一个字符串 "Hello" 呢?那不是炸了?所以我们要用 TS 里的泛型去约束它。
function create<T>(C: { new () }) { return new C() }
两个括号里写的是 new ()
说明传入的 C 是可以用 new C()
的。然后我们再加个上返回类型和传入的类型,可以写成这样。
function create<T>(c: { new (): T }): T { return new c() }