ZhongAnTech / zarm

基于 React 的移动端 UI 组件库
https://zarm.design
MIT License
1.71k stars 225 forks source link

建议 Modal.Confirm 支持自定义布局 #814

Closed lencx closed 1 year ago

lencx commented 3 years ago

需求背景

因需求要求,弹窗内的 确认 / 取消 按钮,与组件布局不一致时,需要重写 Confirm 内的组件布局。

解决方案

支持函数组件对 Confirm 重新布局,通过参数导出 onOKonCancel 方法,供用户使用。

Modal.confirm({
  title: '标题',
  content: ({ onOk, onCancel }) => {
    const handleOk = () => {
      // code
      onOk();
    };
    const handleCancel = () => {
      // code
      onCancel();
    };
    return (
      <div>
        <div>content</div>
        <footer>
          <button onClick={handleOk}>确认</button>
          <button onClick={handleCancel}>取消</button>
        </footer>
      </div>
    )
  },
})
stbui commented 3 years ago

Confirm 布局基本是固定形式,可以通过Modal自定义布局

lencx commented 3 years ago

@stbui Modal 是组件式调用,Confirm 是函数式调用,但是 函数式的 Confirm 却不支持自定义布局。改写或重新实现,相当于对 zarm 进行二次开发,因为这种需求在实际业务开发中,出现频率很高,所以希望官方可以支持。

iShawnWang commented 3 years ago

@lencx 我也想过同样的问题... 确实暂时能想到需要使用 Modal 场景, 都是命令式的方式来控制 Model 的显示隐藏, And D 是支持 Confirm 自定义 content 的, 但之前使用时, 遇到过这个问题 https://github.com/ant-design/ant-design/issues/29291#issuecomment-775869367, 所以 不论改动成本, 即使 Modal.Confirm 支持了自定义 content 内容, 仍会有一些局限


后来做了一个折中的方案, 可以一起讨论下: https://gist.github.com/iShawnWang/95d40403870f27970d84f0a2395d46dc 大意是利用 Context + Hook, 命令式的统一管理 Modal 的显示隐藏和传参

JeromeLin commented 3 years ago

这种情况是不是把 Confirm 的逻辑抽成一个 hooks 组件会更好点。

iShawnWang commented 3 years ago

这种情况是不是把 Confirm 的逻辑抽成一个 hooks 组件会更好点。

可... 晚些我想想, 出个 useModal 示例一起讨论下

iShawnWang commented 3 years ago

只控制显示隐藏的简单版

// useModal.tsx
import { useState, useCallback } from 'react';
import type { ModalProps } from 'antd';

export default (props?: ModalProps) => {
  const [visible, setVisible] = useState(false);
  const hide = useCallback(() => setVisible(false), []);
  return [
    useCallback(() => setVisible(true), []),
    {
      visible,
      onCancel: hide,
      ...props,
    },
    hide,
  ] as const;
};

使用

import { Modal, Button } from 'antd';
import useModal from './useModal1';

export default function IndexPage() {
  const [showXXXModal, modalProps] = useModal();
  return (
    <>
      <Button
        onClick={() => {
          showXXXModal();
        }}
      >
        emm
      </Button>
      <Modal {...modalProps}></Modal>
    </>
  );
}
iShawnWang commented 3 years ago

补充:

  1. useModal 返回值为 [], 用户可以自定义显示隐藏 Modal 的方法名, 也可以不用 hide 方法 对比 :
    
    const { show, hide, modalProps } = useModal()
    ---
    const [ showXXXModal, modalProps ] = useModal()

2. 包装自己的 useModal

import useModal from 'useModal'

export default MyUseModal = (props?: Parameters) => { return useModal({ destroyOnClose: true, ...props }); };

iShawnWang commented 3 years ago

一个邪门歪道激进版

export const useModal2 = (props?: ModalProps) => {
  const [visible, setVisible] = useState(false);
  const hide = useCallback(() => setVisible(false), []);

  const _Modal: React.FC<ModalProps> = memo((_props) => {
    const { children, ...rest } = _props;
    return (
      <Modal visible={visible} onCancel={hide} {...props} {...rest}>
        {children}
      </Modal>
    );
  });
  return [useCallback(() => setVisible(true), []), _Modal, hide] as const;
};

使用超方便

export default function IndexPage() {
  const [showXXXModal, Modal] = useModal2();
  return (
    <>
      <Button
        onClick={() => {
          showXXXModal();
        }}
      >
        emm
      </Button>
      <Modal>111</Modal>
    </>
  );
}

非常好用... 暂时有个缺陷... hide 时没有动画, 直接被 unmount 了

lencx commented 3 years ago

useModal 的局限性:

可以参考 React-Toastify:

import React from 'react';

import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

function App(){
  const notify = () => toast("Wow so easy!");
  return (
    <div>
      <button onClick={notify}>Notify!</button>
      <ToastContainer />
    </div>
  );
}

实现思路:

import React from 'react';
import { ToastContainer, toast } from 'zarm';

function App(){
  // type: info, warn, error, success, confirm ...
  const notify = () => toast.confirm({
    // content
    // onCancel?
    // onOk?
  });

  return (
    <div>
      <button onClick={notify}>Notify!</button>
      <ToastContainer />
    </div>
  );
}

关键代码参考:

JeromeLin commented 3 years ago

@lencx 只试用单例模式的情况使用吧。如果页面上存在多个,或者多级弹层呢?

lencx commented 3 years ago

多例会维护一个 id list

iShawnWang commented 3 years ago

get 了... 需要简单泛用的静态方法 ~