• 这里记录一下查看antd 组件库时学到的东西,或者是想法

源码地址

其实我们可以简单想一下我们需要一个Modal 框那些属性是必要的,很容易想象的到就是visible, onCancel, onOk 这几个属性, 因为这几个属性是
控制我们这个Modal 显示隐藏的

所以就有了以下下简易传值

const { toggle, setToggle } = useToggle();
<Modal 
    visible={toggle} 
    onCancel={() => setToggle(false)} 
    onOk={() => doSome()}
>
</Modal>

查看源码Modal.tsx, 这个组件很简单,其实就是使用了rc-componentrc-dialog, 把我们传的visble, onCancel 等传到 Dialog 里面

import Dialog from 'rc-dialog'

interface Iprop {
    visible: boolean,
    onCancel: () => void,
    onOk: () => void
}

const Modal: React.FC<Iprops> = (props: Iprops) => {
    const { visbile, onCancel, onOk } = props;

    const handleCancel = () => {
        onCancel?.()
    }
    const handleOk = () => {
        onOk?.()
    }

    return (
        <Dialog 
            visible={visible}
            onCancel={handleCancel}
            onOk={handleOk}
        >
    )
}

Modal 中我们还有一些用法,比如:

Modal.confirm({
    title: 'hello,',
    content: 'world',
    onOk() {
        console.log('OK');
    },
    onCancel() {
        console.log('Cancel');
    },
});

这样子可以直接调出Modal

看看Modal.confirm 的实现源码地址

它用了一个高阶函数confirm(withxxx(props)), withxxx函数就是给props 附加一个type, 即 类似Object.assign(props, { type: 'xxx' });

Modal.info = function infoFn(props: ModalFuncProps) {
  return confirm(withInfo(props));
};
  • withInfo 函数
    /** withInfo 函数 */
    function withInfo(props: ModalFuncProps): ModalFuncProps {
    return {
      icon: <InfoCircleOutlined />,
      okCancel: false,
      ...props,
      type: 'info',
    };
    }

重头戏 Confirm 函数

其实就是在body 下面 创建一个节点const div = document.createElement('div'); 然后将div append 到body下, 最后调用 React.render(Modal, div)将model 渲染到div上,

function confirm(config: ModalFuncProps) {
  const div = document.createElement('div');
  document.body.appendChild(div);
  let currentConfig = { ...config, close, visible: true } as any;

    /** destroy 的时候将visbile 改成false,然后如果有onCancel 就执行onCancle, 将当前div remove掉 **/
  function destroy(...args: any[]) {
    const unmountResult = ReactDOM.unmountComponentAtNode(div);
    if (unmountResult && div.parentNode) {
      div.parentNode.removeChild(div);
    }
    const triggerCancel = args.some(param => param && param.triggerCancel);
    if (config.onCancel && triggerCancel) {
      config.onCancel(...args);
    }
    for (let i = 0; i < destroyFns.length; i++) {
      const fn = destroyFns[i];

      if (fn === close) {
        destroyFns.splice(i, 1);
        break;
      }
    }
  }

    /** render 到div上  */
  function render(props: { visible: boolean, onOk: () => void, onCancel: () => void }) {

    setTimeout(() => {
      ReactDOM.render(
        <ConfirmDialog
          {...props}
        />,
        div,
      );
    });
  }

    /** close 的时候将visbile 改成false,然后如果有afterClose afterClose, 然后destroy掉 **/
  function close(...args: any[]) {
    currentConfig = {
      ...currentConfig,
      visible: false,
      afterClose: () => {
        if (typeof config.afterClose === 'function') {
          config.afterClose();
        }
        destroy.apply(this, args);
      },
    };
    render(currentConfig);
  }

    /** 向外暴露update 方法,可以修改props等,修改完props然后render一下 */
  function update(configUpdate: ConfigUpdate) {
    if (typeof configUpdate === 'function') {
      currentConfig = configUpdate(currentConfig);
    } else {
      currentConfig = {
        ...currentConfig,
        ...configUpdate,
      };
    }
    render(currentConfig);
  }

  render(currentConfig);

  destroyFns.push(close);

    /** 暴露update 和destroy 方法 */
    /** 可以用 const modal = Modal.info({title: '123'}) */
    /** 可以用 modal.update({title: '345'}) 去更新title*/
    /** 可以用 modal.destory() 去关闭modal*/
  return {
    destroy: close,
    update,
  };
}

注意看这个<ConfirmDialog />

ConfirmDialog

源码

这个ConfirmDialog 里面的确认取消 按钮都用了一个<ActionButton onOk={onOk}/> 其实就是一个<Button />

下面只看他如何处理onOk 方法

const ActionButton = () => {
    const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
    const { close } = props;
    if (!isThenable(returnValueOfOnOk)) {
      return;
    }
    setLoading(true);
        /** promise 完成后直接close 掉 */
    returnValueOfOnOk!.then(
      (...args: any[]) => {
        setLoading(false);
        close(...args);
        clickedRef.current = false;
      },
      (e: Error) => {
        console.error(e);
        setLoading(false);
        clickedRef.current = false;
      },
    );
  };

  const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    const { actionFn, close } = props;
        // 这里处理按钮是否被点击了,如果是, 直接return掉
    if (clickedRef.current) {
      return;
    }
    clickedRef.current = true;
        /** 如果没有onOk 或者是onCancel 方法,直接调用modal.close() 方法,光比弹窗 */
    if (!actionFn) {
      close();
      return;
    }
    let returnValueOfOnOk;
        /** 这里我的理解是 处理是否冒泡? */
    if (props.emitEvent) {
      returnValueOfOnOk = actionFn(e);
            /** 判断是否是一个promise , 处理onOk 直接return new Promise() */
      if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) {
        clickedRef.current = false;
        close(e);
        return;
      }
    } else if (actionFn.length) {
            /** onOk 是否有参数 function onOk(close) 的情况, */
      returnValueOfOnOk = actionFn(close);
      clickedRef.current = false;
    } else {
            /** 无参数 */
      returnValueOfOnOk = actionFn();
      if (!returnValueOfOnOk) {
        close();
        return;
      }
    }
        /** 处理promise 方法 */
    handlePromiseOnOk(returnValueOfOnOk);
  };

    return (
        <Button OnClick={onClick}>
        </Button>
    )
}