TS与React的完美融合:泛型组件

您好,我是沧沧凉凉,是一名前端开发者,目前在掘金知乎以及个人博客上同步发表一些学习前端时遇到的趣事和知识,欢迎关注。


由于JSX的存在,React解锁了JavaScript的完全编程能力,所以它对TypeScript也有着完美的支持,其中泛型组件是React项目使用了TypeScript后才获得一项新技能

在之前的很多文章中,我都提到TypeScript用来写中大型项目拥有非常多的优势,无论是代码提示还是类型校验,都能大大的降低你项目中的隐藏BUG,毫不夸张的说,正确使用TypeScript比使用JavaScript的隐藏BUG少百分之80左右。

那么在讲解泛型组件之前,我们先交代一下TypeScript中的泛型到底是什么:

1. 泛型

先看一下下面这个简单的函数。

function myPrint(p: number) {
  console.log(p);
  return p;
}

myPrint这个函数,参数p被限制为一个数字类型,这个时候如果你想要传入一个字符串类型,那么TypeScript就会进行报错。

image-20210822103522106

但是你就是想要这个函数的参数p不仅能是数字类型,还能是字符串类型,那该怎么办?

可以像下面这样使用联合类型,表示参数p可以是数字类型也可能是字符串类型。

function myPrint(p: number | string) {
  console.log(p);
  return p;
}

但如果这个时候参数p也可以是数组或者对象或者任意的类型呢?那么这个时候我们的any!(划掉)泛型就出场了!

先说一点,将参数p定义为any也是可以解决这个需求的,但是我想说的是!在任何情况下,都尽量不要使用any!因为它会失去类型校验。

我们先来看一下上面的三种函数的返回值有什么不同。

image-20210822104335101

image-20210822104353838

image-20210822104418658

可以看到当参数p声明为any时,该函数的返回值也是any,你可能觉得没什么,因为你自己写的代码你知道这个返回值到底是什么类型,但是如果代码量一多或者别人接手你的代码,又或者过了好多个月后,你还知道res这个值是什么类型嘛?

上面的代码中无论是联合类型还是any都没有完美的解决这个问题,我想要的结果是传入number返回值就是number,传入string返回值就是string,也就是说我想要的是传入什么类型,返回值就是什么类型。

这个时候就需要用到泛型了。

function myPrint<T>(p: T) {
  console.log(p);
  return p;
}

泛型声明很简单,就是在函数名后面加一个<>至于尖括号中的内容,你写什么都可以,但是我们默认约定是T,如果是多个的话那么就是<T,U>

这个时候我们再看一下该函数的返回值。

image-20210822105105401

image-20210822105124556

image-20210822105141940

可以看到,使用泛型后,你传入的是什么类型,那么它返回的就是什么类型,这就是泛型的好处。

2. 泛型组件

那么说了这么多,React的泛型组件又是什么呢?我们先来看一段代码:

// 定义Test的Props
interface TestProps {
  data: string;
  // 这里是重点
  onFinish: (value: string) => void;
}

function Test({ data, onFinish }: TestProps) {
  return (
    <>
      <button
        onClick={() => {
          onFinish(data);
        }}
      >
        数据
      </button>
    </>
  );
}

export default function App() {
  return (
    <div className="App">
      <Test
        data="这是数据"
        // 注意这里的value的类型
        onFinish={(value) => {
          console.log(value);
        }}
      />
    </div>
  );
}

看了上面的代码,不知道你们是否有一种恍然大悟(一脸懵逼)的感觉,这段代码我想表达的意思是,在父组件中使用onFinish想要获取子组件中data的值。

image-20210822110506639

可以看到onFinish中的value是个string类型。

但是我们想让onFinishvalue参数的类型和Testdata参数类型保持一致,即data="这是数据"是一个string类型那么onFinish中的value也为stringdatanumber那么value也为number,换句话说data是什么类型,那么value就应该是什么类型。

至于怎么做,直接看代码:

// 定义Test的Props
interface TestProps<T> {
  data: T;
  // 这里是重点
  onFinish: (value: T) => void;
}

function Test<T>({ data, onFinish }: TestProps<T>) {
  return (
    <>
      <button
        onClick={() => {
          onFinish(data);
        }}
      >
        数据
      </button>
    </>
  );
}

export default function App() {
  return (
    <div className="App">
      <Test
        data="这是数据"
        // 注意这里的data的类型
        onFinish={(value) => {
          console.log(value);
        }}
      />
    </div>
  );
}

将上面的代码进行稍微的调整,加入前面我们讲的泛型,这个时候我们看一下value是什么类型。

image-20210822110805215

可以看到,data的类型是string,那么value也为string,我们将data改成number试一下:

image-20210822110900066

可以看到value也变成了number,那么我们将data改成数组类型呢?

image-20210822110945372

依然能够正确的显示类型,到这里你应该初步了解了React中的泛型组件了吧?

3. 最后

React的泛型组件一般都是用来封装一个通用的组件,比如在Antd UI中,表格组件就用到了泛型声明,在Antd中还有非常多的组件用到了泛型,如果你想要二次封装这些组件,那么你就需要了解泛型。