TS系列(2): 使用泛型提升TS函数可重用性

67 阅读2分钟

本篇是**「一起学习** 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;
  }
}

在这个改进的方案中,你可以看到,我们传了一个类型参数**Tretry函数。然后我们在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 」系列将会更新相关内容,敬请期待!💐💐