本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧50:用条件类型代替声明重载
考虑如何为下面这个函数写type?
function double(x) {
return x + x;
}
double函数的可以传入string或者number。所以你有可能会考虑 union type:
function double(x: number|string): number|string;
function double(x: any) { return x + x; }
这样声明没有问题,但是不够精确:
const num = double(12); // string | number
const str = double('x'); // string | number
我们期待传入string,返回也应该是string。传入number,返回也应该是number。这个声明忽略了这个差别,返回的type使用不方便。
为了解决这个问题,你可能考虑用泛型:
function double<T extends number|string>(x: T): T;
function double(x: any) { return x + x; }
const num = double(12); // Type is 12
const str = double('x'); // Type is "x"
很不幸,这种方法太过精确了。我们传入string type,返回 string literal type。这是错误的,当我们传入'x',我们期待返回的type为'xx', 实际返回的type为:'x'.
还有一个解决办法,对一个函数写多个类型声明,也称函数声明重载:
function double(x: number): number;
function double(x: string): string;
function double(x: any) { return x + x; }
const num = double(12); // Type is number
const str = double('x'); // Type is string
这有效!但是有点不幸,有一个潜在bug,当传入的类型string或者number,都没有问题。但是传入string和number的union type就会报错:
function f(x: number|string) {
return double(x);
// ~ Argument of type 'string | number' is not assignable
// to parameter of type 'string'
}
我们调用方法是安全的,应该返回 sting | number类型。当你定义了函数声明重载,ts会根据声明重载一个一个去检查:是否有满足你调用的声明类型,直到找到满足的类型声明。 这里先检查number类型不满足 你的 string | number 。继续往下:string不满足你的string | number。就报错了!
其实最好的解决办法是条件类型:
function double<T extends number | string>(
x: T
): T extends string ? string : number;
function double(x: any) { return x + x; }
这有点像前面的泛型,但是这里对T的类型做了约束,返回类型加了条件判断。 这样方法能让我们所有的测试用例通过:
const num = double(12); // number
const str = double('x'); // string
// function f(x: string | number): string | number
function f(x: number|string) {
return double(x);
}
所以当我们写声明重载的时候,考虑有没有合适的条件类型进行替代。