原文链接:https://leanpub.com/essentialtypescript/read#leanpub-auto-generics
在静态语言中,如C++、C#、Java,generic 是为了让代码具备一定的动态类型,以便于减少重复性。而Javascript本身就是动态类型语言,为什么还需要generic呢?我想,是为了增加可读性,同时增加“静态性”,给编译器提供一些类型信息,让它给我们提供一些限制,以免代码写得过于随意了。
1. generic functions
function clone(value) {
let serialized = JSON.stringify(value);
return JSON.parse(serialized);
}
这是js中典型的clone函数,这个函数的输入和输出应该是同一种类型的对象。如何保证这一点呢?使用 Generic:
function clone<T>(value: T) {
let serialized = JSON.stringify(value);
return JSON.parse(serialized);
}
说明:
1. 只是第一行有变化,在函数名和参数列表之间加了 <T>, 给参数指定类型 T;
2. T 只是一种惯用法,可以写任何字符串,只要符合变量命名规则即可。
当调用这个函数时,鼠标悬停在函数名上,可以看到TypeScript识别出来的类型:
2. generic classes
在js中,Array 本身就是generic类型的:
var nums: number[] = [1,2];
var nums: Array<Number> = [1,2];
这两种写法完全等价。
定义一个 generic 的键值对class:
class KeyValuePair<TKey, TValue> {
constructor(public key: TKey,
public value: TValue) {
}
}
let pair1 = new KeyValuePair(1, 'First');
let pair2 = new KeyValuePair('Second', Date.now());
let pair3 = new KeyValuePair(3, 'Third');
把鼠标悬停在任意变量上,TypeScript 都能识别出类型,如:
我们也可以明确指定 Key 和Value的类型:
let pair1 = new KeyValuePair<number, string>(1, 'First');
let pair2 = new KeyValuePair<string, Date>('Second', Date.now());
let pair3 = new KeyValuePair<number, string>(3, 'Third');
如果构造函数传入的数值与指定的类型不同,编译器会报错。这时,在Visual Studio Code中,我们会看到 pair2 的 Date.now() 下面有错误提示。因为,Date.now() 的返回值时number,不是Date。
在更复杂的情况下,TypeScript 也可以推断出类型信息,例如:
class KeyValuePairPrinter<T,U> {
constructor(private pairs: KeyValuePair<T,U>[]) {
}
print() {
for(let p of this.pairs) {
console.log(`${p.key}: ${p.value}`);
}
}
}
let printer = new KeyValuePairPrinter([pair1, pair2, pair3]);
printer.print();
第12行有编译错误,因为,pair2 和 其它两个变量类型不同。
3. generic constraints
回顾一下这个函数:
function totalLength(x: {length: number}, y:{length: number}) {
var total: number = x.length + y.length;
return total;
}
看起来挺完美,但我们无法避免这种情况:
var length = totalLength('Jess', [1,2,3]);
把字符串和数组的length相加没有什么意义。
有了generic,可以这么改:
function totalLength<T>(x: T, y: T) {
var total: number = x.length + y.length;
return total;
}
这样可以保证两个参数类型相同,但,又不能保证它们都有 length 属性。
Generic constraints 来了:
function totalLength<T extends { length: number }>(x: T, y: T) {
var total: number = x.length + y.length;
return total;
}
说明:
1. extends 关键字,前面是T,后面是一个匿名 interface;
2. 我们曾经见过extends,在类的 “继承” 时用过它;
这里可以用任何interface,不必是匿名的,如:
interface IHaveALength {
length: number
}
function totalLength<T extends IHaveALength>(x: T, y: T) {
var total: number = x.length + y.length;
return total;
}
这样,TypeScript可以明确的识别出下面的代码是否合法:
var l1 = totalLength([1,2], [1,2,3]);
var l2 = totalLength('Less', [1,2,3]);
Generic 也兼容子类型,例如,我们定义一个Array的子类:
class CustomArray<T> extends Array<T> {
toJson(): string {
return JSON.stringify(this);
}
}
这种用法是合法的:
var length = totalLength([1,2], new CustomArray<number>());