基于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
/** 自定義底部?jī)?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)邏輯來(lái)處理按鈕的顯示、點(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 組件接收一系列屬性,并通過(guò) 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')
在上述代碼中,通過(guò)向 withType 函數(shù)傳入特定的 type 參數(shù)來(lái)創(chuàng)建不同類型的 Modal 組件。
接下來(lái),對(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)文章
使用Node搭建reactSSR服務(wù)端渲染架構(gòu)
這篇文章主要介紹了使用Node搭建reactSSR服務(wù)端渲染架構(gòu),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
react使用useImperativeHandle示例詳解
這篇文章主要為大家介紹了react使用useImperativeHandle示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
react如何實(shí)現(xiàn)一個(gè)密碼強(qiáng)度檢測(cè)器詳解
這篇文章主要給大家介紹了關(guān)于react如何實(shí)現(xiàn)一個(gè)密碼強(qiáng)度檢測(cè)器的相關(guān)資料,使用這個(gè)密碼強(qiáng)度器后可以幫助大家提高在線帳號(hào)、個(gè)人信息的安全性,需要的朋友可以參考下2021-06-06
react進(jìn)階教程之異常處理機(jī)制error?Boundaries
在react中一旦出錯(cuò),如果每個(gè)組件去處理出錯(cuò)情況則比較麻煩,下面這篇文章主要給大家介紹了關(guān)于react進(jìn)階教程之異常處理機(jī)制error?Boundaries的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
React?Hook中的useState函數(shù)的詳細(xì)解析
Hook 就是 JavaScript 函數(shù),這個(gè)函數(shù)可以幫助你鉤入(hook into) React State以及生命周期等特性,這篇文章主要介紹了React?Hook?useState函數(shù)的詳細(xì)解析的相關(guān)資料,需要的朋友可以參考下2022-10-10
詳解三種方式在React中解決綁定this的作用域問(wèn)題并傳參
這篇文章主要介紹了詳解三種方式在React中解決綁定this的作用域問(wèn)題并傳參,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

