如何優(yōu)雅的使用ant.design的Modal組件蜂怎?

一個(gè)數(shù)據(jù)列表少不了數(shù)據(jù)的增虐先、刪、改派敷,一般我們都是通過(guò)Modal+Form實(shí)現(xiàn)表單數(shù)據(jù)的獲取,在一個(gè)復(fù)雜的數(shù)據(jù)列表中我們需要好多個(gè)Modal+Form的配合撰洗,這時(shí)候我們需要維護(hù)好多個(gè)Modal的visible狀態(tài)篮愉,例如下面的場(chǎng)景:

const List = () => {
  const [visible1, setVisible1] = useState(false);
  const [visible2, setVisible2] = useState(false);
  const [visible3, setVisible3] = useState(false);
  const [visible4, setVisible4] = useState(false);
  const [visible5, setVisible5] = useState(false);
  const [visible6, setVisible6] = useState(false);
  return(
    <div>
      <Modal visible={visible1}><Form1/></Modal>
      <Modal visible={visible2}><Form2/></Modal>
      <Modal visible={visible3}><Form3/></Modal>
      <Modal visible={visible4}><Form4/></Modal>
      <Modal visible={visible5}><Form5/></Modal>
      <Modal visible={visible6}><Form6/></Modal>
    <div/>
  )
}

姑且不算其他代碼就Modal和state我們就寫(xiě)了將近20行代碼,那如何減少M(fèi)odal和state的維護(hù)呢差导?

封裝useModal實(shí)現(xiàn)Modal優(yōu)雅的使用:

首先试躏,我們看一下預(yù)期最簡(jiǎn)單的使用方式,我們只需要調(diào)用modal.open方法時(shí)设褐,替換children就可達(dá)到Modal重復(fù)使用颠蕴。

export default () => {
  const [modal, ModalDOM] = useModal();

  const handleClick = () => {
    modal.open({
      title: '收集數(shù)據(jù)',
      children: <Form1/>,
      onOk: (values: any) => { // values為收集到的表單d
        modal.close();
      }
    });
  }

  return (
    <>
      <button onClick={handleClick}>自定義useModal</button>
      {ModalDOM}
    </>
  )
}

上面我們寫(xiě)的預(yù)期效果,我們知道useModal返回了一個(gè)鉤子和一個(gè)Modal組件助析,通過(guò)調(diào)用鉤子的open方法和close方法來(lái)控制Modal組件打開(kāi)和關(guān)閉犀被,下面我們通過(guò)預(yù)期,逆向?qū)崿F(xiàn)主體函數(shù):

interface modalRefType {
  open: () => void;
  close: () => void;
  injectChildren: (child: React.ReactElement) => void;
  injectModalProps: (props: ModalProps) => void;
}

export default () => {
  const modalRef = useRef<modalRefType>();
  const handle = useMemo(() => {
    return {
      open: ({ children, ...rest }) => {
        modalRef.current.injectChildren(children);  // 注入子組件
        modalRef.current.injectModalProps(rest);    // 注入Modal的參數(shù)
        modalRef.current.open();
      },
      close: () => {
        modalRef.current.close();
      }
    };
  }, []);

  return [handle, <MyModal ref={modalRef} />] as const;
}

主體函數(shù)思路還是比較清晰外冀,通過(guò)handle+useRef控制MyModal組件暴露出來(lái)的injectChildren寡键、injectModalProps、open雪隧、close方法西轩,下面我們?cè)俑鶕?jù)暴露出來(lái)的這些方法,逆向編程實(shí)現(xiàn)MyModal組件:

const MyModal = memo(forwardRef((prop: any, ref) => {
  const [form] = Form.useForm();
  const [modalChildren, setModalChildren] = useState<React.ReactElement>(null);
  const [modalProps, setModalProps] = useState<ModalProps>({
    visible: false,
  });

  // ant.design 4.0 Form的onFinish觸發(fā)回調(diào)
  const onFinish = useCallback((values: any) => {
    modalProps.onOk?.(values);
  }, [form, modalProps]);

  // 關(guān)閉當(dāng)前Modal
  const onClose = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: false,
    }));
  }, [form]);

  // 關(guān)閉當(dāng)前Modal
  const onOpen = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: true,
    }));
  }, [form]);


  useImperativeHandle(ref, () => ({
    // 注入Modal的子組件
    injectChildren: (element) => {
      setModalChildren(element);
    },
    // 注入Modal參數(shù)
    injectModalProps: (props) => {
      console.log(props)
      setModalProps((source) => {
        return {
          ...source,
          ...props,
        }
      });
    },
    // 打開(kāi)Modal
    open: () => {
      onOpen();
    },
    // 關(guān)閉Modal
    close: () => {
      onClose();
    }
  }), []);

  // 這里的Modal是ant.design中的Modal
  return (
    <Modal
      {...modalProps}
      onCancel={onClose}
      onOk={() => form.submit()}. 
    >
      {
        modalChildren
        ?
        React.cloneElement(modalChildren, {
          onFinish,
          form,
          onClose,
        })
        : null
      }
    </Modal>
  )
}));

我們通過(guò)React.cloneElement克隆了子組件(就是我們的業(yè)務(wù)代碼Form)脑沿,并且注入form和onFinish實(shí)現(xiàn)對(duì)表單的控制藕畔,通過(guò)onOk方法的挾持,實(shí)現(xiàn)在點(diǎn)擊Modal的ok按鈕時(shí)獲取表單數(shù)據(jù)庄拇。

以上代碼有一個(gè)細(xì)節(jié)注服,我們?cè)诟淖儬顟B(tài)的時(shí)候都是使用函數(shù)的方式setModalProps((sourcce) => ({...source, ...})),這主要是為了獲取最新的狀態(tài)進(jìn)行。

到目前為止祠汇,我們已經(jīng)實(shí)現(xiàn)了useModal基礎(chǔ)版本仍秤,它只能讓我們創(chuàng)建表單,而無(wú)法編輯表單和脫離表單去使用可很。

進(jìn)階實(shí)現(xiàn)useModal

為了脫離Form使用和表單的編輯诗力,我們添加了type、initialValues參數(shù):

....  
// 修改使用方式
  const handleClick = () => {
    modal.open({
      title: '收集數(shù)據(jù)',
      type: 'form',   // 類(lèi)型為form時(shí)需要Form觸發(fā)onFinish才觸發(fā)onOk
      initialValues: { name: '原值' },  // 表單默認(rèn)值
      children: <Form1/>,
      onOk: (values: any) => {
        console.log('收集到的表單', values);
        modal.close();
      }
    });
  }
  ....

升級(jí)后useModal完整的代碼:

import React, { useRef, useMemo, memo, forwardRef, useCallback, useState, useImperativeHandle } from 'react';
import { Modal, Form } from 'antd';
import type { ModalProps } from 'antd';
import "antd/dist/antd.css";

const MyModal = memo(forwardRef((prop: any, ref) => {
  const [form] = Form.useForm();
  const [modalChildren, setModalChildren] = useState<React.ReactElement>(null);
  const [modalProps, setModalProps] = useState<ModalProps>({
    visible: false,
  });
  const typeRef = useRef<string>();

  // ant.design 4.0 Form的onFinish觸發(fā)回調(diào)
  const onFinish = useCallback((values: any) => {
    modalProps.onOk?.(values);
  }, [form, modalProps]);

  // 關(guān)閉當(dāng)前Modal
  const onClose = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: false,
    }));
  }, [form]);

  // 關(guān)閉當(dāng)前Modal
  const onOpen = useCallback(() => {
    setModalProps((source) => ({
      ...source,
      visible: true,
    }));
  }, [form]);


  useImperativeHandle(ref, () => ({
    // 注入Modal的子組件
    injectChildren: (element) => {
      setModalChildren(element);
    },
    // 注入Modal參數(shù)
    injectModalProps: (props) => {
      console.log(props)
      setModalProps((source) => {
        return {
          ...source,
          ...props,
        }
      });
    },
    // 打開(kāi)Modal
    open: () => {
      onOpen();
    },
    // 關(guān)閉Modal
    close: () => {
      onClose();
    },
    // 設(shè)置表單數(shù)據(jù)
    setFieldsValue: (values: any) => {
      form.setFieldsValue?.(values);
    },
    setType: (type: string) => {
      typeRef.current = type;
    }
  }), []);

  const handleOk = useCallback(() => {
    if (typeRef.current === 'form') {
      form.submit();
    } else {
      modalProps.onOk?.(null);
    }
  }, [form, modalProps]);

  // 這里的Modal是ant.design中的Modal
  return (
    <Modal
      {...modalProps}
      onCancel={onClose}
      onOk={handleOk}
    >
      {
        modalChildren
        ?
        React.cloneElement(modalChildren, {
          onFinish,
          form,
          onClose,
        })
        : null
      }
    </Modal>
  )
}));

interface modalRefType {
  open: () => void;
  close: () => void;
  injectChildren: (child: React.ReactElement) => void;
  injectModalProps: (props: ModalProps) => void;
  setFieldsValue: (values: any) => void;
  setType: (type: string) => void;
}

interface openArgType extends ModalProps {
  children?: React.ReactElement,
  type?: 'form' | 'default',
  initialValues?: {
    [key: string]: any;
  },
}

export default () => {
  const modalRef = useRef<modalRefType>();
  const handle = useMemo(() => {
    return {
      open: ({ children, type, initialValues, ...rest }: openArgType) => {
        modalRef.current.setType(type);
        modalRef.current.injectChildren(children);  // 注入子組件
        modalRef.current.injectModalProps(rest);    // 注入Modal的參數(shù)
        modalRef.current.open();

        if (initialValues && type === 'form') {
          modalRef.current.setFieldsValue?.(initialValues);
        }
      },
      close: () => {
        modalRef.current.close();
      }
    };
  }, []);

  return [handle, <MyModal ref={modalRef} />] as const;
}

完結(jié):以上我們通過(guò)了逆向的編程方式我抠,實(shí)現(xiàn)了我們需求的useModal鉤子苇本,用這樣的一個(gè)hook使用Modal是不是優(yōu)雅多了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末菜拓,一起剝皮案震驚了整個(gè)濱河市瓣窄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纳鼎,老刑警劉巖俺夕,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贱鄙,居然都是意外死亡劝贸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)逗宁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)映九,“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了漠嵌?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)引有。 經(jīng)常有香客問(wèn)我,道長(zhǎng)管挟,這世上最難降的妖魔是什么轿曙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮僻孝,結(jié)果婚禮上导帝,老公的妹妹穿的比我還像新娘。我一直安慰自己穿铆,他們只是感情好您单,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著荞雏,像睡著了一般虐秦。 火紅的嫁衣襯著肌膚如雪平酿。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,394評(píng)論 1 310
  • 那天悦陋,我揣著相機(jī)與錄音蜈彼,去河邊找鬼。 笑死俺驶,一個(gè)胖子當(dāng)著我的面吹牛幸逆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播暮现,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼还绘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了栖袋?” 一聲冷哼從身側(cè)響起拍顷,我...
    開(kāi)封第一講書(shū)人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎塘幅,沒(méi)想到半個(gè)月后昔案,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡电媳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年爱沟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匆背。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖身冀,靈堂內(nèi)的尸體忽然破棺而出钝尸,到底是詐尸還是另有隱情,我是刑警寧澤搂根,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布珍促,位于F島的核電站,受9級(jí)特大地震影響剩愧,放射性物質(zhì)發(fā)生泄漏猪叙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一仁卷、第九天 我趴在偏房一處隱蔽的房頂上張望穴翩。 院中可真熱鬧,春花似錦锦积、人聲如沸芒帕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)背蟆。三九已至鉴分,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間带膀,已是汗流浹背志珍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留垛叨,地道東北人伦糯。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像点额,于是被迫代替她去往敵國(guó)和親舔株。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容