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 分钟前
解决 IntelliJ IDEA Build时 Lombok 不生效问题
java·前端·intellij-idea
chancygcx_34 分钟前
前端核心技术Node.js(二)——path模块、HTTP与模块化
前端·http·node.js
YGY_Webgis糕手之路36 分钟前
Cesium 快速入门(三)Viewer:三维场景的“外壳”
前端·gis·cesium
丘色果1 小时前
NPM打包时,报reason: getaddrinfo ENOTFOUND registry.nlark.com
前端·npm·node.js
姜太小白1 小时前
【前端】CSS Flexbox布局示例介绍
前端·css
我命由我123451 小时前
Spring Boot 项目问题:Web server failed to start. Port 5566 was already in use.
java·前端·jvm·spring boot·后端·spring·java-ee
南囝coding1 小时前
最近Vibe Coding的经验总结
前端·后端·程序员
前端小咸鱼一条2 小时前
React组件化的封装
前端·javascript·react.js
随便起的名字也被占用2 小时前
leaflet中绘制轨迹线的大量轨迹点,解决大量 marker 绑定 tooltip 同时显示导致的性能问题
前端·javascript·vue.js·leaflet
南方kenny2 小时前
TypeScript + React:让前端开发更可靠的黄金组合
前端·react.js·typescript