如何编写高阶组件的范型

223 阅读3分钟

在业务中部分逻辑需要抽取到高阶组件里面去,而编写 高阶组件的范型就需要我们格外的注意,否则业务组件没有提示

  1. 在 React 中使用多语言方案,通常采用 react-i18next 这个 npm 包,我们在业务中一般像如下这种方式使用
// 业务 A 组件
import { useTranslation } from "react-i18next";
const Company = () => {
  const { t } = useTranslation();

  return (
    <div>
     {t('xxx.yy.zzz')}
    </div>
  );
};
export default Company;
  1. 正常使用 react-i18next 确实没什么问题,但是我们需要在每个使用到 多语言的地方 重复 import, 重复const { t } = useTranslation(); 这样才能够正常拿到 t。 这也不是不可以,但是做了太多重复的事情。基于 D.R.Y 原则,可以把这里封装成高阶组件 (高阶组件内容本身好写, 高阶组件范型没那么好写)

  2. 这里的高阶组件类型就有问题

image.png

  1. 多语言 高阶组件
// 定义的 多语言 高阶组件
import { useTranslation } from "react-i18next";
import type { ComponentType } from "react";

export default function withWrapperCom<TProps>(
  WrappendCom: ComponentType<TProps & WithWrapperComType>,
) {
  return (props: TProps) => {
    const { t } = useTranslation();
    return (
      <WrappendCom
        t={t}
        {...props}
      />
    );
  };
}

// 定义的 高阶组件 范型
export type WithWrapperComType = {
    t: TFunction<"translation", undefined>;
};

使用高阶组件

import withWrapperCom, { WithWrapperComType } from '@/components/withWrapperCom'

type TypeCompany: object 
const Company = (props: TypeCompany & WithWrapperComType) => {
  const { t } = props.t;

  return (
    <div>
     {t('xxx.yy.zzz')}
    </div>
  );
};
export default withWrapperCom(Company);

基于上面的高阶组件,虽然已经完成了功能,并且可以正常运行,但是 使用方 每次在使用 高阶组件都需要导入相应的范型WithWrapperComType,比较麻烦, 且不利于使用 babel plugin 进行自动化 动态插入 高阶组件(因为 WithWrapperComType 是基于 compile time,不是基于 run time)

  1. 改善高阶组件

    1. 定义的高阶组件如下
    2. 高阶组件的 WithWrapperComType 单独 export 出去,方便后续有特殊业务可以单独 import 使用, 正常情况下 使用 HideComPropsWithChildren 即可满足业务需求
// 高阶组件
import { useTranslation } from "react-i18next";
import type { ComponentType } from "react";
export default function withWrapperCom<TProps>(
  WrappendCom: ComponentType<TProps & WithWrapperComType>,
) {
  return (props: TProps) => {
    const { t } = useTranslation();
    return (
      <WrappendCom
        t={t}
        {...props}
      />
    );
  };
}

// 定义的 高阶组件 范型
export type WithWrapperComType = {
    t: TFunction<"translation", undefined>;
};
  1. 定义的 全局 globalWithHideCom.d.ts 文件 (此文件需要加入到 tsconfig.json 里面)
//globalWithHideCom.d.ts

import type { PropsWithChildren } from "react";
import type { WithWrapperComType } from "@/components/withWrapperCom";

declare global {
  type HideComPropsWithChildren<T> = PropsWithChildren<T & WithWrapperComType>;
}
  1. 业务方使用
// 业务 A 组件
import withWrapperCom from '@/components/withWrapperCom'

type TypeCompany: object 
const Company = (props: HideComPropsWithChildren<TypeCompany>) => {
  const { t } = props.t;

  return (
    <div>
     {t('xxx.yy.zzz')}
    </div>
  );
};
export default withWrapperCom(Company);

最终:

  1. 业务方在使用的时候可以直接使用 withWrapperCom 包装业务组件, 且 高阶组件的 范型也 不需要每次都导入 (这里定义了 HideComPropsWithChildren 范型,使用时直接把 业务组件的范型传入 HideComPropsWithChildren 即可)
  2. 就本case来说, TProps 才是真正业务传递的范型。定义的高阶组件的参数 WrappendCom 在实际调用的时候有传递参数 t, 所以定义的业务组件 A 的范型里面有t(定义 A组件的范型需要使用 HideComPropsWithChildren 进行传递), 对应的 定义的高阶组件的参数 WrappendCom 的范型 也需要传递 t。(这里需要明确清楚知道 定义的组件 和 实际调用的组件 不一致,且范型也不一致, 什么时候调用 真实的业务组件,什么时候调用被高阶之后的组件)
  3. 使用 babel plugin 自动注入 自定义高阶组件链接