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

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

相关推荐
wordbaby20 小时前
一行看懂高阶函数:用 handleConfirm 拿下 DatePicker 回调
前端·react.js
卿·静20 小时前
Node.js对接即梦AI实现“千军万马”视频
前端·javascript·人工智能·后端·node.js
Mintopia21 小时前
🚀 Next.js 全栈 Web Vitals 监测与 Lighthouse 分析
前端·javascript·全栈
Mintopia21 小时前
🤖 AIGC + CMS:内容管理系统智能化的核心技术支撑
前端·javascript·aigc
HelloGitHub21 小时前
这款开源调研系统越来越“懂事”了
前端·开源·github
whysqwhw21 小时前
hippy的主要原理
前端
子兮曰21 小时前
🚀95%的前端开发者都踩过坑:JavaScript循环全解析,从基础到高阶异步迭代
前端·javascript·性能优化
2401_8534068821 小时前
Tdesign-React 组件 Card 实现头部固定,内容区单独可滚动
前端·react.js·tdesign
蓝倾97621 小时前
小红书获取用户作品列表API接口操作指南
java·服务器·前端·python·电商开放平台·开放api接口
小桥风满袖21 小时前
极简三分钟ES6 - 数值的扩展
前端·javascript