基于React實(shí)現(xiàn)調(diào)用式Modal組件的全流程
組件的設(shè)計(jì)與實(shí)現(xiàn)
實(shí)現(xiàn) ModalContext
關(guān)于 Context 的概念和具體使用,可以閱讀 React Context 官方文檔。
首先,根據(jù)可能需要自定義的屬性,構(gòu)建 ModalContext
對(duì)象:
// Context.tsx import { createContext, useContext } from 'react' import type { ButtonProps, ModalProps as _ModalProps } from '@nextui-org/react' export type ModalFooterParams = { ConfirmButton: (props: ButtonProps) => React.ReactNode CancelButton: (props: ButtonProps) => React.ReactNode onClose: () => void } export type ModalFooterRenderer = (params: ModalFooterParams) => React.ReactNode export type ModalType = 'default' | 'primary' | 'success' | 'warning' | 'danger' export type ModalProps = { /** 模態(tài)框類型 */ type?: ModalType /** 模態(tài)框標(biāo)題 */ title?: React.ReactNode /** 模態(tài)框內(nèi)容 */ content?: React.ReactNode /** 自定義底部內(nèi)容 */ footer?: ModalFooterRenderer | React.ReactNode /** 關(guān)閉回調(diào) */ onClose?: () => void /** 確認(rèn)按鈕文本 */ confirmButtonText?: string /** 確認(rèn)按鈕屬性 */ confirmButtonProps?: ButtonProps /** 確認(rèn)按鈕圖標(biāo) */ confirmButtonIcon?: React.ReactNode /** 確認(rèn)回調(diào) */ onConfirm?: () => void /** 確認(rèn)前的校驗(yàn)回調(diào) */ beforeConfirm?: () => boolean /** 確認(rèn)按鈕加載狀態(tài) */ isConfirmLoading?: boolean /** 取消按鈕文本 */ cancelButtonText?: string /** 取消按鈕屬性 */ cancelButtonProps?: ButtonProps /** 取消按鈕圖標(biāo) */ cancelButtonIcon?: React.ReactNode /** 取消回調(diào) */ onCancel?: () => void } & Partial<Omit<_ModalProps, 'title' | 'content'>> export const ModalContext = createContext<ModalProps>({} as ModalProps) export const useModalContext = () => { return useContext(ModalContext) }
實(shí)現(xiàn) ModalFooter 組件
首先,定義按鈕的基本屬性以及與之對(duì)應(yīng)的顏色映射關(guān)系。
隨后,分別對(duì) ConfirmButton
、CancelButton
以及 ModalFooter
組件展開具體的實(shí)現(xiàn),各組件內(nèi)根據(jù)獲取到的上下文屬性及相關(guān)邏輯來處理按鈕的顯示、點(diǎn)擊等行為。具體代碼如下:
// Footer.tsx import { useState } from 'react' import { Button, ButtonProps, ModalFooter as _ModalFooter, useModalContext as _useModalContext, } from '@nextui-org/react' import { useModalContext } from './Context' export const COLOR_MAP = { default: 'bg-black dark:text-black dark:bg-white', primary: 'bg-secondary', success: 'bg-success', warning: 'bg-warning', danger: 'bg-red-500', } export function ConfirmButton(props: ButtonProps) { const { onClose: _onClose } = _useModalContext() const { type = 'default', confirmButtonText = '確認(rèn)', confirmButtonProps, confirmButtonIcon, onClose, onConfirm, beforeConfirm, isConfirmLoading, } = useModalContext() const [isLoading, setIsLoading] = useState(false) const onClick = async () => { if (confirmButtonProps?.type === 'submit') { return } setIsLoading(!!isConfirmLoading) try { const isConfirm = await beforeConfirm?.() if (isConfirm !== false) { await onConfirm?.() onClose?.() _onClose() } } finally { setIsLoading(false) } } return ( <Button color={type} className={`w-max py-2 px-8 rounded-lg flex items-center gap-2 text-white border-slate-400 ${COLOR_MAP[type]}`} startContent={confirmButtonIcon} isLoading={isLoading} onClick={onClick} {...props} {...confirmButtonProps}> {confirmButtonText} </Button> ) } export function CancelButton(props: ButtonProps) { const { onClose: _onClose } = _useModalContext() const { cancelButtonText = '取消', cancelButtonProps, cancelButtonIcon, onCancel, onClose, } = useModalContext() const onClick = () => { onCancel?.() onClose?.() _onClose() } return ( <Button className='rounded-lg text-inherit bg-transparent border border-black/40 dark:border-white hover:bg-default-100' startContent={cancelButtonIcon} onClick={onClick} {...props} {...cancelButtonProps}> {cancelButtonText} </Button> ) } export function ModalFooter() { const { onClose } = _useModalContext() const { footer } = useModalContext() const defaultFooter = ( <div className='flex items-center gap-2'> <CancelButton /> <ConfirmButton /> </div> ) if (typeof footer === 'function') { return <_ModalFooter>{footer({ ConfirmButton, CancelButton, onClose })}</_ModalFooter> } return footer !== null ? ( <_ModalFooter>{footer || defaultFooter}</_ModalFooter> ) : null }
實(shí)現(xiàn) Modal 組件
Modal
組件接收一系列屬性,并通過 ModalContext.Provider
將這些屬性傳遞給子組件。另外,Modal
組件對(duì)彈窗的關(guān)閉邏輯進(jìn)行了整合;當(dāng)我們關(guān)閉模態(tài)框時(shí),會(huì)依次觸發(fā) onCancel
和 onClose
回調(diào)函數(shù),確保關(guān)閉流程的可控性。具體代碼如下:
// Modal.tsx import { Modal as _Modal, ModalBody, ModalHeader, ModalContent, ModalFooter as _ModalFooter, useModalContext as _useModalContext, } from '@nextui-org/react' import { ModalContext, type ModalProps } from './Context' import { ModalFooter } from './Footer' import { withType } from './modal' export function Modal(props: ModalProps) { const { title, content, footer, onClose: _onClose, children, confirmButtonText, confirmButtonProps, onConfirm, beforeConfirm, isConfirmLoading, cancelButtonText, cancelButtonProps, onCancel, ...restProps } = props function onClose() { onCancel?.() _onClose?.() } return ( <ModalContext.Provider value={props}> <_Modal disableAnimation className='m-auto' onClose={onClose} {...restProps}> <ModalContent> <ModalHeader>{title}</ModalHeader> <ModalBody className='text-sm'> {content || children} </ModalBody> <ModalFooter /> </ModalContent> </_Modal> </ModalContext.Provider> ) } Modal.default = withType('default') Modal.primary = withType('primary') Modal.success = withType('success') Modal.warning = withType('warning') Modal.danger = withType('danger')
在上述代碼中,通過向 withType
函數(shù)傳入特定的 type
參數(shù)來創(chuàng)建不同類型的 Modal
組件。
接下來,對(duì) Modal
組件的渲染邏輯以及創(chuàng)建不同類型 Modal
組件的方法進(jìn)行封裝,相關(guān)代碼如下:
// modal.tsx import { isValidElement } from 'react' import { createRoot } from 'react-dom/client' import { Modal } from './Modal' import type { ModalProps, ModalType } from './Context' function modal(config: ModalProps) { const currentConfig = { ...config, isOpen: true, onClose } const container = document.createDocumentFragment() const root = createRoot(container) function render(config: ModalProps) { root.render(<Modal {...config} />) } function onClose() { render({ ...currentConfig, isOpen: false, }) setTimeout(function () { root.unmount() }, 300) } render(currentConfig) return { onClose, } } export function withType(type: ModalType) { function _withType( content: React.ReactNode | ModalProps, config?: Omit<ModalProps, 'content'> ): Promise<void> { const _config = isValidElement(content) || typeof content === 'string' ? { ...config, content } : content as ModalProps return new Promise((resolve, reject) => { const onConfirm = async () => { await _config.onConfirm?.() resolve() } const onCancel = async () => { await _config.onCancel?.() reject() } modal({ ..._config, onConfirm, onCancel, type, }) }) } return _withType }
組件的使用方式
該組件提供了三種預(yù)期使用方式。
Modal.success("操作成功!"); Modal.success({ title: "提示", content: "操作成功!", confirmButtonText: "好的", }); Modal.success("操作成功!", { title: "提示", confirmButtonText: "好的", });
傳入配置對(duì)象
定義 handleClick
函數(shù),隨后在函數(shù)內(nèi)部調(diào)用 Modal
組件,并傳入配置對(duì)象:
import { type ModalType } from '@/components/Modal/Context' import { COLOR_MAP } from '@/components/Modal/Footer' import { Modal } from '@/components/Modal' import { Button } from '@nextui-org/react' export default function Home() { const colorList: ModalType[] = ['default', 'primary', 'success', 'warning', 'danger'] const handleClick = (type: ModalType) => { Modal[type]({ title: '提示', content: `${type} 操作成功!`, confirmButtonText: '好的', cancelButtonText: '取消', }) } return ( <div className='py-12 flex gap-2 items-center justify-center'> {colorList.map((color) => ( <Button key={color} color={color} className={`rounded-lg text-white ${COLOR_MAP[color]}`} onClick={() => handleClick(color)} > {color} Click </Button> ))} </div> ) }
效果分別如下:
傳入文本內(nèi)容
對(duì) handleClick
函數(shù)中 Modal
組件的調(diào)用形式進(jìn)行修改,并且只傳入文本內(nèi)容:
const handleClick = (type: ModalType) => { Modal[type](`${type} 操作成功!`) }
效果分別如下:
傳入文本內(nèi)容和配置對(duì)象
修改 handleClick
函數(shù)中 Modal
組件的調(diào)用形式,同時(shí)傳入文本內(nèi)容與配置對(duì)象:
const handleClick = (type: ModalType) => { Modal[type](`${type} 操作成功!`, { title: '提示', confirmButtonText: '好的', cancelButtonText: '取消', }) }
效果分別如下:
至此,調(diào)用式 Modal
組件的實(shí)現(xiàn)流程已全部完成。
最后
以上就是基于React實(shí)現(xiàn)調(diào)用式Modal組件的全流程的詳細(xì)內(nèi)容,更多關(guān)于React調(diào)用式Modal組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React實(shí)現(xiàn)雙滑塊交叉滑動(dòng)
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)雙滑塊交叉滑動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09react實(shí)現(xiàn)Radio組件的示例代碼
這篇文章主要介紹了react實(shí)現(xiàn)Radio組件的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04基于Webpack4和React hooks搭建項(xiàng)目的方法
這篇文章主要介紹了基于Webpack4和React hooks搭建項(xiàng)目的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-02-02使用React?SSR寫Demo一學(xué)就會(huì)
這篇文章主要為大家介紹了使用React?SSR寫Demo實(shí)現(xiàn)教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06React實(shí)現(xiàn)父組件調(diào)用子組件的兩種寫法
react通信分很多種,比如:父子通信,兄弟通信等等,這里我們就簡單說一下父子通信,父子通信分為:父組件調(diào)用子組件里面的方法;子組件調(diào)用子組件里面的方法,這里我們著重說一下父組件調(diào)用子組件,需要的朋友可以參考下2024-04-04React配置Redux并結(jié)合本地存儲(chǔ)設(shè)置token方式
這篇文章主要介紹了React配置Redux并結(jié)合本地存儲(chǔ)設(shè)置token方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01React、Vue中key的作用詳解 (key的內(nèi)部原理解析)
key是虛擬DOM對(duì)象的標(biāo)識(shí),當(dāng)狀態(tài)中的數(shù)據(jù)發(fā)生變化時(shí),Vue會(huì)根據(jù)[新數(shù)據(jù)]生成[新的虛擬DOM],本文給大家介紹React、Vue中key的作用詳解 (key的內(nèi)部原理解析),感興趣的朋友一起看看吧2023-10-10