react封装指令式组件

react指令式这个说法第一次接触是在antd组件库中,当时感觉眼前一亮,就觉得很多东西会变得很方便,将很多无关紧要的组件从显式声明解耦出来,也减少了页面中变量状态的维护

于是当时扒了扒源码,借鉴了一下,封装了一个万用的组件

首先封装了一个hook,这个hook决定了调用声明式组件的api和组件显示的位置

ts 复制代码
import usePatchElement from 'antd/es/_util/hooks/usePatchElement';
import React, { useMemo, useState } from 'react';
import { useCreation } from 'ahooks';
import { createPortal } from 'react-dom';
import { InstructionMountArgs, TsUtils } from '@/utils/TsUtils';

interface Apis {
  closeFn: (result?: any) => void,
  promise: Promise<any>
}

export const useElementsContextHolder = () => {
  const [elements, patchElement] = usePatchElement();
  const [instanceMap] = useState(new Map<string, Apis>());

  const fns = useCreation(() => ({
    instructionMountPromise<P, R>({ Component, props, getContainer }: InstructionMountArgs<P>) {
      const { promise, resolve } = TsUtils.getSeparatePromise<R>();
      const key = TsUtils.getUUID();
      let element: React.ReactElement = React.createElement(Component, { ...props, key, closeResolve: (data?: R) => this.closeElement(key, data) });
      if (getContainer) {
        element = createPortal(element, getContainer);
      }
      const removeElementFn = patchElement(element);
      const closeFn: Apis['closeFn'] = (result) => {
        removeElementFn();
        instanceMap.delete(key);
        resolve(result);
      };
      instanceMap.set(key, {
        closeFn,
        promise,
      });

      return promise;
    },
    closeElement<R>(key: string, result?: R) {
      instanceMap.get(key).closeFn(result);
    },
  }), []);

  return [fns, (
    <React.Fragment>
      {elements}
    </React.Fragment>
  )] as const;
};

api最好是全局调用,当然不是全局也可以,毕竟迭代中总会出现一些额外的情况

这里我将api挂载在一个全局的appInstance中

ts 复制代码
export class AppInstance {
  ContextHolder: ReturnType<typeof useElementsContextHolder>[0];

  getContextHolder() { return this.ContextHolder; }

  setContextHolder(ContextHolder: ContextHolderPlugin['ContextHolder']) {
    this.ContextHolder = ContextHolder;
  }

  ContextHolderComponent() {
    const [contextHolderApi, contextHolder] = useElementsContextHolder();
    useMount(() => {
      this.setContextHolder(contextHolderApi);
    });

    return contextHolder;
  }
}

<AntdConfigProvider>
   <appInstance.ContextHolderComponent />
</AntdConfigProvider>

最后一步就是包装指令式命令了,我这里用antd的选择文件举例,antd的选择文件需要显式声明,而包装之后只需在点击中promise等待就够了

ts 复制代码
import React, { FC, useRef } from 'react';
import { Upload, UploadProps } from 'antd';
import { css, cx } from '@emotion/css';
import { useMount } from 'ahooks';
import { InstructionMountProps } from '@/utils/TsUtils';
import { appInstance } from '@/runTime';

const SelectFiles: FC<UploadProps & InstructionMountProps> = (props) => {
  const ref = useRef<HTMLDivElement>();

  useMount(() => {
    ref.current.click();
  });

  return (
    <div className={cx(css`height: 0;overflow: hidden`)}>
      <Upload
        {...props}
        beforeUpload={(file, FileList) => {
          props.closeResolve(FileList);
          return Upload.LIST_IGNORE;
        }}
      >
        <div ref={ref}>上传</div>
      </Upload>
    </div>
  );
};

export const openSelectFilesPromise = (args: UploadProps) => {
  return appInstance.getContextHolder().instructionMountPromise<UploadProps, File[]>({
    Component: SelectFiles,
    props: args,
  });
};

例如我有一个流程:弹框输入文件名->选择文件->上传文件->弹框提示上传成功

按照声明式流程,至少需要显式声明两个弹框一个上传按钮,还需要变量控制是否显示弹框,但是使用指令式封装之后只需要全部在异步流程中就可以了

ts 复制代码
async click() {
    const name = await openInputModal();
    const [file] = await openSelectFilesPromise();
    await uploadFile();
    await showUploadSuccessModal();
}

显式声明写的太麻烦了,这里就不写了,比指令式要麻烦不知道多少

相关推荐
小小小小宇28 分钟前
前端转后端基础- 变量和类型
前端
Cobyte1 小时前
1.基于依赖追踪和触发的响应式系统的本质
前端·javascript·vue.js
主宰者1 小时前
C# CommunityToolkit.Mvvm全局事件
java·前端·c#
前端小咸鱼一条2 小时前
16.迭代器 和 生成器
开发语言·前端·javascript
小江的记录本2 小时前
【注解】常见 Java 注解系统性知识体系总结(附《全方位对比表》+ 思维导图)
java·前端·spring boot·后端·spring·mybatis·web
web守墓人2 小时前
【前端】记一次将ruoyi vue3 element-plus迁移到arco design vue的经历
前端·vue.js·arco design
伊步沁心2 小时前
Webpack & Vite 深度解析
前端
libokaifa2 小时前
OpenSpec + TDD:让 AI 写代码,用测试兜底
前端·ai编程
用户15815963743702 小时前
搭 AI Agent 团队踩了 18 个坑,总结出这 5 个关键步骤
前端
Kellen2 小时前
Fumadocs 基础概念:从内容源到页面渲染
前端