本篇是**「一起学习** TypeScript **」**系列的第2篇,接下来笔者将持续深耕,不定时更新一系列精彩纷呈的TypeScript文章,旨在解答使用TypeScript过程中的各种疑难杂症,一起探索TS的无穷魅力!
【TS系列回顾】:
TS系列(1): React中能否使子组件实现“类型安全”?
一、一个小挑战
进入正文之前,先来思考一个问题:下面的代码中,我们的retry 函数无法推断出已经resolved的promise类型,你知道怎么解决嘛?🤔
async function retry(
fn: () => Promise<any>,
retries: number = 5
): Promise<any> {
try {
return await fn();
} catch (err) {
if (retries > 0) {
console.log("Retrying...");
return await retry(fn, retries - 1);
}
throw err;
}
}
const getString = () => Promise.resolve("hello");
const getNumber = () => Promise.resolve(42);
retry(getString).then((str) => {
// str should be string, not any!
console.log(str);
});
retry(getNumber).then((num) => {
// num should be number, not any!
console.log(num);
});
二、为什么会发生错误?
上面代码中的错误之所以会出现,是因为我们使用了 any 作为 retry函数里promise的返回类型:
async function retry(
fn: () => Promise<any>,
retries: number = 5
): Promise<any> {
// ...}
}
这意味着当我们调用retry函数时,TS不能推断出已经处于resolved状态的promise类型,只会显示any!
这么写当然不合适,any的使用会禁用掉一切类型检查。也就是说,当我们使用retry函数时,不管我们传入什么参数,都将失去类型安全!😣😣
当你在TS中需要重用一个函数时,这是一个常见的问题——惰性使我们在函数上添加一个any类型就完事了。但是,实际上我们只需要一些额外的小工作,就能使我们的TS函数更加灵活且类型安全。
三、解决方案:使用类型参数
代替使用any,我们可以使用一个类型参数(type parameter) 使上述的retry函数更加灵活,如下所示👇🏻:
async function retry<T>(
fn: () => Promise<T>,
retries: number = 5
): Promise<T> {
try {
return await fn();
} catch (err) {
if (retries > 0) {
console.log("Retrying...");
return await retry(fn, retries - 1);
}
throw err;
}
}
在这个改进的方案中,你可以看到,我们传了一个类型参数**T给retry函数。然后我们在fn的参数中将T引用为我们期望从Promise中返回的内容。最后,我们使用T**作为retry的返回类型。
现在,当我们调用retry函数时,TS就可以推断出已完成的promise类型。
const getString = () => Promise.resolve("hello");
retry(getString).then((str) => {
// str是string类型, 不再是any!
console.log(str);
});
✍🏻PS: 这里的类型参数
T你也可以换成其他任何名字,例如:TData或者TResponse等等。不过很多TS开源项目中代码大多使用T来命名。
其实,这么写之后,我们的retry函数就是一个泛型函数(generic function)——它能捕获运行时我们传入的类型信息 !
如果您有兴趣了解有关泛型(Generic)的更多信息,后续「一起学习 TypeScript 」系列将会更新相关内容,敬请期待!💐💐