react antd confirm content list_React造轮系列:对话框组件 - Dialog 思路
React造輪系列:對(duì)話框組件 - Dialog 思路
對(duì)話框一般是我們點(diǎn)擊按鈕彈出的這么一個(gè)東西,主要類型有 Alter, Confirm 及 Modal, Modal 一般帶有半透明的黑色背景。當(dāng)然外觀可參考 AntD 或者 Framework 等。
確定 API
API 方面主要還是要參考同行,因?yàn)槿绻幸惶?#xff0c;別人想你用的UI框架時(shí),你的 API 跟他之前常用的又不用,這樣就加大了入門門檻,所以API 盡量保持跟現(xiàn)有的差不多。
對(duì)話框除了提供顯示屬性外,還要有點(diǎn)擊確認(rèn)后的回放函數(shù),如:
alert('你好').then(fn)confirm('確定?').then(fn)modal(組件名)實(shí)現(xiàn)
Dialog 源碼已經(jīng)上傳到這里(https://github.com/qq449245884/frank-react2-test)。
dialog/dialog.example.tsx, 這里 state ,生命周期使用 React 16.8 新出的 Hook,如果對(duì) Hook 不熟悉可以先看官網(wǎng)文檔。
dialog/dialog.example.tsx
import React, {useState} from 'react'import Dialog from './dialog'export default function () { const [x, setX] = useState(false) return ( {setX(!x)}}>點(diǎn)擊 )}dialog/dialog.tsx
import React from 'react'interface Props { visible: boolean}const Dialog: React.FunctionComponent = (props) => { return ( props.visible ? dialog : null )}export default Dialog運(yùn)行效果
顯示內(nèi)容
上述還有問(wèn)題,我們 dialog 在組件內(nèi)是寫死的,我們想的是直接通過(guò)組件內(nèi)包裹的內(nèi)容,如:
// dialog/dialog.example.tsx...hi...這樣寫,頁(yè)面上是不會(huì)顯示 hi 的,這里 children 屬性就派上用場(chǎng)了,我們需要在 dialog 組件中進(jìn)一步驟修改如下內(nèi)容:
// dialog/dialog.tsx...return ( props.visible ? {props.children} : null)...顯示遮罩
通常對(duì)話框會(huì)有一層遮罩,通常我們大都會(huì)這樣寫:
// dialog/dialog.tsx...props.visible ? {props.children} : null...這種結(jié)構(gòu)有個(gè)不好的地方就是點(diǎn)擊遮罩層的時(shí)候要關(guān)閉對(duì)話框,如果是用這種結(jié)構(gòu),用戶點(diǎn)擊任何 div,都相當(dāng)于點(diǎn)擊遮罩層,所以最好要分開(kāi):
// dialog/dialog.tsx... {props.children} ...由于 React 要求最外層只能有一個(gè)元素, 所以我們多用了一個(gè) div 包裹起來(lái),但是這種方法無(wú)形之中多了個(gè) div,所以可以使用 React 16 之后新出的 Fragment, Fragment 跟 vue 中的 template 一樣,它是不會(huì)渲染到頁(yè)面的。
import React, {Fragment} from 'react'import './dialog.scss';interface Props { visible: boolean}const Dialog: React.FunctionComponent = (props) => { return ( props.visible ? {props.children} : null )}export default Dialog完善頭部,內(nèi)容及底部
這里不多說(shuō),直接上代碼
import React, {Fragment} from 'react'import './dialog.scss';import {Icon} from '../index'interface Props { visible: boolean}const Dialog: React.FunctionComponent = (props) => { return ( props.visible ? 提示 {props.children} ok cancel : null )}export default Dialog從上述代碼我們可以發(fā)現(xiàn)我們寫樣式的名字時(shí)候,為了不被第三使用覆蓋,我們自定義了一個(gè) fui-dialog前綴,在寫每個(gè)樣式名稱時(shí),都要寫一遍,這樣顯然不太合理,萬(wàn)一哪天我不用這個(gè)前綴時(shí)候,每個(gè)都要改一遍,所以我們需要一個(gè)方法來(lái)封裝。
咱們可能會(huì)寫這樣方法:
function scopedClass(name) { return `fui-dialog-${name}`}這樣寫不行,因?yàn)槲覀?name 可能不傳,這樣就會(huì)多出一個(gè) -,所以需要進(jìn)一步的判斷:
function scopedClass(name) { return `fui-dialog-${name ? '-' + name : ''}`}那還有沒(méi)有更簡(jiǎn)潔的方法,使用 filter 方法:
function scopedClass(name ?: string) { return ['fui-dialog', name].filter(Boolean).join('-')}調(diào)用方式如下:
... 提示 {props.children} ok cancel ...大家在想法,這樣寫是有問(wèn)題,每個(gè)組件都寫一個(gè)函數(shù)嗎,如果 Icon 組件,我還需要寫一個(gè)fui-icon, 解決方法是把 前綴當(dāng)一個(gè)參數(shù),如:
function scopedClass(name ?: string) { return ['fui-dialog', name].filter(Boolean).join('-')}調(diào)用方式如下:
className={scopedClass('fui-dialog', 'mask')}這樣寫,還不如直接寫樣式,這種方式是等于白寫了一個(gè)方法,那怎么辦?這就需要高階函數(shù)出場(chǎng)了。實(shí)現(xiàn)如下:
function scopeClassMaker(prefix: string) { return function (name ?: string) { return [prefix, name].filter(Boolean).join('-') }}const scopedClass = scopeClassMaker('fui-dialog')scopeClassMaker 函數(shù)是高級(jí)函數(shù),返回一個(gè)帶了 prefix 參數(shù)的函數(shù)。
事件處理
在寫事件處理之前,我們 Dialog 需要接收一個(gè) buttons 屬性,就是顯示的操作按鈕并添加事件:
// dialog/dialog.example.tsx... {setX(false)}}>1, {setX(false)}}>2, ]}> hi...咱們看到這個(gè),第一反應(yīng)應(yīng)該是覺(jué)得這樣寫很麻煩,我寫個(gè) dialog, visible要自己,按鈕要自己,連事件也要自己寫。請(qǐng)接受這種設(shè)定。雖然麻煩,但非常的好理解。這跟 Vue 的理念是不太一樣的。當(dāng)然后面會(huì)進(jìn)一步驟優(yōu)化。
組件內(nèi)渲染如下:
{ props.buttons }運(yùn)行起來(lái)你會(huì)發(fā)現(xiàn)有個(gè)警告:
主要是說(shuō)我們渲染數(shù)組時(shí),需要加個(gè) key,解決方法有兩種,就是不要使用數(shù)組方式,當(dāng)然這不治本,所以這里 React.cloneElemen 出場(chǎng)了,它可以克隆元素并添加對(duì)應(yīng)的屬性值,如下:
{ props.buttons.map((button, index) => { React.cloneElement(button, {key: index}) })}對(duì)應(yīng)的點(diǎn)擊關(guān)閉事件相對(duì)容易這邊就不講了,可以自行查看源碼。
接下來(lái)來(lái)看一個(gè)樣式的問(wèn)題,首先先給出我們遮罩的樣式:
.fui-dialog { position: fixed; background: white; min-width: 20em; z-index: 2; border-radius: 4px; top: 50%; left: 50%; transform: translate(-50%, -50%); &-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: fade_out(black, 0.5); z-index: 1; } .... 以下省略其它樣式}我們遮罩 .fui-dialog-mask 使用 fixed 定位感覺(jué)是沒(méi)問(wèn)題的,那如果在調(diào)用 dialog 同級(jí)在加以下這么元素:
666 {setX(!x)}}>點(diǎn)擊...運(yùn)行效果:
發(fā)現(xiàn)遮罩并沒(méi)有遮住 666 的內(nèi)容。這是為什么?
看結(jié)構(gòu)也很好理解,遮罩元素與 666 是同級(jí)結(jié)構(gòu),且層級(jí)比 666 低,當(dāng)然是覆蓋不了的。那咱們可能就會(huì)這樣做,給 .fui-dialog-mask設(shè)置一個(gè) zIndex 比它大的唄,如 9999。
效果:
恩,感覺(jué)沒(méi)問(wèn)題,這時(shí)我們?cè)?Dialog 組件在嵌套一層 zIndex 為 9 的呢,如:
...運(yùn)行效果如下:
發(fā)現(xiàn),父元素被壓住了,里面元素 zIndex 值如何的高,都沒(méi)有效果。
那這要怎么破?答案是不要讓它出現(xiàn)在任何元素的里面,這怎么可能呢。這里就需要引出一個(gè)神奇的 API了。這個(gè) API 叫做 傳送門(portal)。
用法如下:
return ReactDOM.createPortal( this.props.children, domNode);第一個(gè)參數(shù)就是你的 div,第二個(gè)參數(shù)就是你要去的地方。
import React, {Fragment, ReactElement} from 'react'import ReactDOM from 'react-dom'import './dialog.scss';import {Icon} from '../index'import {scopedClassMaker} from '../classes'interface Props { visible: boolean, buttons: Array, onClose: React.MouseEventHandler, closeOnClickMask?: boolean}const scopedClass = scopedClassMaker('fui-dialog')const sc = scopedClassconst Dialog: React.FunctionComponent = (props) => { const onClickClose: React.MouseEventHandler = (e) => { props.onClose(e) } const onClickMask: React.MouseEventHandler = (e) => { if (props.closeOnClickMask) { props.onClose(e) } } const x = props.visible ? 提示 {props.children} { props.buttons.map((button, index) => { React.cloneElement(button, {key: index}) }) } : null return ( ReactDOM.createPortal(x, document.body) )}Dialog.defaultProps = { closeOnClickMask: false}export default Dialog運(yùn)行效果:
當(dāng)然這樣,如果 Dialog 層級(jí)比同級(jí)的 zIndex 小的話,還是覆蓋不了。 那 zIndex 一般設(shè)置成多少比較合理。一般 Dialog 這層設(shè)置成 1, mask 這層設(shè)置成 2。定的越小越好,因?yàn)橛脩艨梢匀ジ摹?/p>
zIndex 的管理
zIndex 管理一般就是前端架構(gòu)師要做的了,根據(jù)業(yè)務(wù)產(chǎn)景來(lái)劃分,如廣告肯定是要在頁(yè)面最上面,所以 zIndex 一般是屬于最高級(jí)的。
便利的 API 之 Alert
上述我們使用 Dialog 組件調(diào)用方式比較麻煩,寫了一堆,有時(shí)候我們想到使用 alert 直接彈出一個(gè)對(duì)話框這樣簡(jiǎn)單方便。如
example 3
alert('1')}>alert我們想直接點(diǎn)擊 button ,然后彈出我們自定義的對(duì)話框內(nèi)容為1 ,需要在 Dialog 組件內(nèi)我們需要導(dǎo)出一個(gè) alert 方法,如下:
// dialog/dialog.tsx...const alert = (content: string) => { const component = {}}> {content} const div = document.createElement('div') document.body.append(div) ReactDOM.render(component, div)}export {alert}...運(yùn)行效果:
但有個(gè)問(wèn)題,因?yàn)閷?duì)話框的 visible 是由外部傳入的,且 React 是單向數(shù)據(jù)流的,在組件內(nèi)并不能直接修改 visible,所以在 onClose 方法我們需要再次渲染一個(gè)新的組件,并設(shè)置新組件 visible 為 ture,覆蓋原來(lái)的組件:
...const alert = (content: string) => { const component = { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() }}> {content} const div = document.createElement('div') document.body.append(div) ReactDOM.render(component, div)}..便利的 API 之 confirm
confirm 調(diào)用方式:
confirm('1', ()=>{}, ()=> {})}>confirm第一個(gè)參數(shù)是顯示的內(nèi)容,每二個(gè)參數(shù)是確認(rèn)的回調(diào),第三個(gè)參數(shù)是取消的回調(diào)函數(shù)。
實(shí)現(xiàn)方式:
const confirm = (content: string, yes?: () => void, no?: () => void) => { const onYes = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() yes && yes() } const onNo = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() no && no() } const component = ( { onNo()}} buttons={[yes, no ]} > {content} ) const div = document.createElement('div') document.body.appendChild(div) ReactDOM.render(component, div)}事件處理跟 Alter 差不多,唯一多了一步就是 confirm 當(dāng)點(diǎn)擊 yes 或者 no 的時(shí)候,如果外部有回調(diào)就需要調(diào)用對(duì)應(yīng)的回調(diào)函數(shù)。
便利的 API 之 modal
modal 調(diào)用方式:
{modal(你好
)}}>modalmodal 對(duì)應(yīng)傳遞的內(nèi)容就不是單單的文本了,而是元素。
實(shí)現(xiàn)方式:
const modal = (content: ReactNode | ReactFragment) => { const onClose = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() } const component = {content} const div = document.createElement('div') document.body.appendChild(div) ReactDOM.render(component, div)}注意,這邊的 content 類型。
運(yùn)行效果:
這還有個(gè)問(wèn)題,如果需要加按鈕呢,可能會(huì)這樣寫:
{modal(你好 close
)}}>modal這樣是關(guān)不了的,因?yàn)?Dialog 是封裝在 modal 里面的。如果要關(guān),必須控制 visible,那很顯然我從外面控制不了里面的 visible,所以這個(gè) button 沒(méi)有辦法把這個(gè) modal 關(guān)掉。
解決方法就是使用閉包,我們可以在 modal 方法里面把 close 方法返回:
const modal = (content: ReactNode | ReactFragment) => { const onClose = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() } const component = {content} const div = document.createElement('div') document.body.appendChild(div) ReactDOM.render(component, div) return onClose;}最后多了一個(gè) retrun onClose,由于閉包的作用,外部調(diào)用返回的 onClose 方法可以訪問(wèn)到內(nèi)部變量。
調(diào)用方式:
const openModal = () => { const close = modal(你好 close()}>close
)}modal重構(gòu) API
在重構(gòu)之前,我們先要抽象 alert, confirm, modal 中各自的方法:
從表格可以看出,modal 與其它兩個(gè)只多了一個(gè) retrun api,其實(shí)其它兩個(gè)也可以返回對(duì)應(yīng)的 Api,只是我們沒(méi)去調(diào)用而已,所以補(bǔ)上:
這樣一來(lái),這三個(gè)函數(shù)從抽象層面上來(lái)看是類似的,所以這三個(gè)函數(shù)應(yīng)該合成一個(gè)。
首先抽取公共部分,先取名為 x ,內(nèi)容如下:
const x= (content: ReactNode, buttons ?:Array, afterClose?: () => void) => { const close = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() afterClose && afterClose() } const component = { close(); afterClose && afterClose() }} buttons={buttons} > {content} const div = document.createElement('div') document.body.append(div) ReactDOM.render(component, div) return close}alert 重構(gòu)后的代碼如下:
const alert = (content: string) => { const button = close()}>ok const close = x(content, [button])}confirm 重構(gòu)后的代碼如下:
const confirm = (content: string, yes?: () => void, no?: () => void) => { const onYes = () => { close() yes && yes() } const onNo = () => { close() no && no() } const buttons = [ yes, no ] const close = modal(content, buttons, no)}modal 重構(gòu)后的代碼如下:
const modal = (content: ReactNode | ReactFragment) => { return x(content)}最后發(fā)現(xiàn)其實(shí) x 方法就是 modal 方法,所以更改 x 名為 modal,刪除對(duì)應(yīng)的 modal 定義。
總結(jié)
本組件為使用優(yōu)化樣式,如果有興趣可以自行優(yōu)化,本節(jié)源碼已經(jīng)上傳至這里(https://github.com/qq449245884/frank-react2-test)中的 lib/dialog。
總結(jié)
以上是生活随笔為你收集整理的react antd confirm content list_React造轮系列:对话框组件 - Dialog 思路的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 天玥电脑用的什么cpu(天玥电脑生产厂家
- 下一篇: 直方图 帕累托图_如何发现现象背后的关键