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();
}

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

相关推荐
你的人类朋友20 分钟前
🤔什么时候用BFF架构?
前端·javascript·后端
知识分享小能手37 分钟前
Bootstrap 5学习教程,从入门到精通,Bootstrap 5 表单验证语法知识点及案例代码(34)
前端·javascript·学习·typescript·bootstrap·html·css3
一只小灿灿1 小时前
前端计算机视觉:使用 OpenCV.js 在浏览器中实现图像处理
前端·opencv·计算机视觉
前端小趴菜051 小时前
react状态管理库 - zustand
前端·react.js·前端框架
Jerry Lau2 小时前
go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南
前端·golang·gin
我命由我123452 小时前
前端开发问题:SyntaxError: “undefined“ is not valid JSON
开发语言·前端·javascript·vue.js·json·ecmascript·js
0wioiw02 小时前
Flutter基础(前端教程③-跳转)
前端·flutter
落笔画忧愁e2 小时前
扣子Coze纯前端部署多Agents
前端
海天胜景2 小时前
vue3 当前页面方法暴露
前端·javascript·vue.js
GISer_Jing2 小时前
前端面试常考题目详解
前端·javascript