封装自定义hook - useLayer - 3. 优化TypeScript类型提示

1. 背景:

回顾我们在第一篇列的一些问题:

  • 1.TypeScript类型优化,我们可以考虑增加泛型,让我们的open/close方法的参数更加准确

  • 2.open方法可以增加参数,用于拓展弹层组件和子组件的props

  • 3.目前的实现仅局限于antd视图,可以实现兼容其他UI库的情况

  • 4.注入的props(injectProps)目前是写死的字段,可以根据实际要求进行转换

  • 5.schema的定义需要引入type,可以考虑提供createLayerSchemas方法,协助生成

其中,3和4两点我们在上一篇已经解决。我们这一篇可以优化TypeScript类型提示

我们可以实现的类型提示优化:

SchemaItem["children"]的类型提示(弹层的子组件我们注入了一些props,但需要开发者手动声明)

比如下面的例子,我们期望实际上使用的参数类型应为"modal" | "drawer"

const schemas: Schemas = [
  {
    key: "modal",
    type: "Modal",
    props: { title: "弹窗" },
    childrenProps: { text: "Modal" },
    children: ({text}) => <div>弹窗内容{text}</div>,
  },
  {
    key: "drawer",
    type: "Drawer",
    props: { title: "抽屉" },
    childrenProps: {},
    children: () => <div>抽屉内容</div>,
  },
];

export default () => {
  const { views, actions } = useLayer(schemas);
  const { modalView, drawerView } = views;
  return (
    <div>
      {modalView}
      {drawerView}
      <Button
        onClick={() => {
          // 实际上这一步有bug,但是open的参数是string,所以没有类型校验提示
          actions.open("modal1");
        }}
      >
        打开弹窗
      </Button>
      <Button
        onClick={() => {
          actions.open("drawer");
        }}
      >
        打开抽屉
      </Button>
    </div>
  );
};

此外,除了上述的优化项,我们也需要考虑让弹层组件能够拿到action,确保子组件内部也能有开关弹层的方法,但这也需要对子组件的类型声明进行优化

可以提供辅助函数,协助用户生成Schema,避免用户需要手动引入type。

2. Actions的类型优化

我们先看useLayer hook现在的类型定义:

 function useLayer(schemas: Schemas, options?: LayerContextType): {
    actions: Actions;
    views: Views;
}

我们也可以对useLayer传入泛型,从而约束Actions和SchemaItem的类型:

 function useLayer<LayerKeys extends string = any>(schemas: SchemaItem<LayerKeys>[], options?: LayerContextType): {
    actions: Actions<LayerKeys>;
    views: Views;
}
/**单个弹层的约定数据
 * @param type 弹层类型 "Modal" | "Drawer"
 * @param key 弹层的唯一标识
 * @param defaultOpen 是否默认打开弹层,默认不打开弹层
 * @param props 弹层的props ModalProps | DrawerProps
 * @param children 弹层的内容 ComponentType<any>
 * @param childrenProps 弹层的内容props
 * @example 
 * {
    type: "Modal",
    key: "modalKey",
    props: {title: "弹窗"},
    children: () => <div>弹窗内容</div>,
    childrenProps:{text:"Modal"}
    }
 */
export type SchemaItem<LabelKey extends string = any> = {
  /**弹层类型 */
  type: LayerType;
  /**弹层的唯一标识 */
  key: LabelKey;
  /**是否默认打开弹层,默认不打开弹层  */
  defaultOpen?: boolean;
  /**弹层的props */
  props: ModalProps | DrawerProps;
  /**弹层的内容 */
  children: ComponentType<any>;
  /**弹层的内容props */
  childrenProps?: Record<string, any>;
};
/**弹层的操作方法 */
export type Actions<LabelKey extends string = any> = {
  /**关闭对应key的弹层 */
  close: (layerKey: LabelKey) => void;
  /**关闭所有弹层 */
  closeAll: () => void;
  /**打开对应key的弹层 */
  open: (layerKey: LabelKey) => void;
};

通过这个方式,可以实现actions方法的类型提示

import { useLayer } from "./implements/index";
import { type SchemaItem } from "./implements/typing";
import { Button } from "antd";

type LayerKeys = "modal" | "drawer";
const schemas: SchemaItem<LayerKeys>[] = [
  {
    key: "modal",
    type: "Modal",
    props: { title: "弹窗" },
    childrenProps: { text: "Modal" },
    children: ({ text }) => <div>弹窗内容{text}</div>,
  },
  {
    key: "drawer",
    type: "Drawer",
    props: { title: "抽屉" },
    childrenProps: {},
    children: () => <div>抽屉内容</div>,
  },
];

3.createLayerSchemas辅助方法

实现一个辅助方法,协助用户生成相应类型的Schema。

实现的方式,其实类似vite的defineConfig方法

import { type SchemaItem } from "./typing";

export function createLayerSchemas<LabelKeys extends string = any>(
  schemas: SchemaItem<LabelKeys>[]
): SchemaItem<LabelKeys>[] {
  return schemas;
}

调用:

import { createLayerSchemas } from "./implements/helper";

const schemas = createLayerSchemas([
  {
    key: "modal",
    type: "Modal",
    props: { title: "弹窗" },
    childrenProps: { text: "Modal" },
    children: ({ text }) => <div>弹窗内容{text}</div>,
  },
  {
    key: "drawer",
    type: "Drawer",
    props: { title: "抽屉" },
    childrenProps: {},
    children: () => <div>抽屉内容</div>,
  },
]);

对应的类型定义:

createLayerSchemas<"modal" | "drawer">(schemas: SchemaItem<"modal" | "drawer">[]): SchemaItem<"modal" | "drawer">[]

4. 总结:

我们可以通过泛型去约束Actions以及SchemaItem的类型,实现更准确的类型定义

6. 源码+运行地址:

CodeSandBox