本篇內(nèi)容介紹了“如何使用React Portals實現(xiàn)一個功能強大的抽屜組件”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
成都創(chuàng)新互聯(lián)公司是一家集成都做網(wǎng)站、網(wǎng)站制作、成都外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站頁面設(shè)計、網(wǎng)站優(yōu)化SEO優(yōu)化為一體的專業(yè)的建站公司,已為成都等多地近百家企業(yè)提供網(wǎng)站建設(shè)服務(wù)。追求良好的瀏覽體驗,以探求精品塑造與理念升華,設(shè)計最適合用戶的網(wǎng)站頁面。 合作只是第一步,服務(wù)才是根本,我們始終堅持講誠信,負責(zé)任的原則,為您進行細心、貼心、認(rèn)真的服務(wù),與眾多客戶在蓬勃發(fā)展的市場環(huán)境中,互促共生。
在開始組件設(shè)計之前希望大家對css3和js有一定的基礎(chǔ),并了解基本的react/vue語法.我們先看看實現(xiàn)后的組件效果:
按照之前筆者總結(jié)的組件設(shè)計原則,我們第一步是要確認(rèn)需求. 一個抽屜(Drawer)組件會有如下需求點:
能控制抽屜是否可見
能手動配置抽屜的關(guān)閉按鈕
能控制抽屜的打開方向
關(guān)閉抽屜時是否銷毀里面的子元素(這個問題是工作中頻繁遇到的問題)
指定 Drawer 掛載的 HTML 節(jié)點, 可以將抽屜掛載在任何元素上
點擊蒙層可以控制是否允許關(guān)閉抽屜
能控制遮罩層的展示
能自定義抽屜彈出層樣式
可以設(shè)置抽屜彈出層寬度
能控制彈出層層級
能控制抽屜彈出方向(上下左右)
點擊關(guān)閉按鈕時能提供回調(diào)供開發(fā)者進行相關(guān)操作
需求收集好之后,作為一個有追求的程序員, 會得出如下線框圖:
對于react選手來說,如果沒用typescript,建議大家都用PropTypes, 它是react內(nèi)置的類型檢測工具,我們可以直接在項目中導(dǎo)入. vue有自帶的屬性檢測方式,這里就不一一介紹了.
通過以上需求分析, 是不是覺得一個抽屜組件要實現(xiàn)這么多功能很復(fù)雜呢? 確實有點復(fù)雜,但是不要怕,有了上面精確的需求分析,我們只需要一步步按照功能點實現(xiàn)就好了.對于我們常用的table組件, modal組件等其實也需要考慮到很多使用場景和功能點, 比如antd的table組件暴露了幾十個屬性,如果不好好理清具體的需求, 實現(xiàn)這樣的組件是非常麻煩的.接下來我們就來看看具體實現(xiàn).
首先我們先根據(jù)需求將組件框架寫好,這樣后面寫業(yè)務(wù)邏輯會更清晰:
import PropTypes from 'prop-types' import styles from './index.less' /** * Drawer 抽屜組件 * @param {visible} bool 抽屜是否可見 * @param {closable} bool 是否顯示右上角的關(guān)閉按鈕 * @param {destroyOnClose} bool 關(guān)閉時銷毀里面的子元素 * @param {getContainer} HTMLElement 指定 Drawer 掛載的 HTML 節(jié)點, false 為掛載在當(dāng)前 dom * @param {maskClosable} bool 點擊蒙層是否允許關(guān)閉抽屜 * @param {mask} bool 是否展示遮罩 * @param {drawerStyle} object 用來設(shè)置抽屜彈出層樣式 * @param {width} number|string 彈出層寬度 * @param {zIndex} number 彈出層層級 * @param {placement} string 抽屜方向 * @param {onClose} string 點擊關(guān)閉時的回調(diào) */ function Drawer(props) { const { closable = true, destroyOnClose, getContainer = document.body, maskClosable = true, mask = true, drawerStyle, width = '300px', zIndex = 10, placement = 'right', onClose, children } = props const childDom = ( <div className={styles.xDrawerWrap}> <div className={styles.xDrawerMask} ></div> <div className={styles.xDrawerContent} { children } { !!closable && <span className={styles.xCloseBtn}>X</span> } </div> </div> ) return childDom } export default Drawer
有了這個框架,我們來一步步往里面實現(xiàn)內(nèi)容吧.
之所以要先實現(xiàn)這幾個功能,是因為他們實現(xiàn)都比較簡單,不會牽扯到其他復(fù)雜邏輯.只需要對外暴露屬性并使用屬性即可. 具體實現(xiàn)如下:
function Drawer(props) { const { closable = true, destroyOnClose, getContainer = document.body, maskClosable = true, mask = true, drawerStyle, width = '300px', zIndex = 10, placement = 'right', onClose, children } = props let [visible, setVisible] = useState(props.visible) const handleClose = () => { setVisible(false) onClose && onClose() } useEffect(() => { setVisible(props.visible) }, [props.visible]) const childDom = ( <div className={styles.xDrawerWrap} style={{ width: visible ? '100%' : '0', zIndex }} > { !!mask && <div className={styles.xDrawerMask} onClick={maskClosable ? handleClose : null}></div> } <div className={styles.xDrawerContent} style={{ width, ...drawerStyle }}> { children } { !!closable && <span className={styles.xCloseBtn} onClick={handleClose}>X</span> } </div> </div> ) return childDom }
上述實現(xiàn)過程值得注意的就是我們組件設(shè)計采用了react hooks技術(shù), 在這里用到了useState, useEffect, 如果大家不懂的可以去官網(wǎng)學(xué)習(xí), 非常簡單,如果有不懂的可以和筆者交流或者在評論區(qū)提問. 抽屜動畫我們通過控制抽屜內(nèi)容的寬度來實現(xiàn),配合overflow:hidden, 后面我會單獨附上css代碼供大家參考.
destroyOnClose主要是用來清除組件緩存,比較常用的場景就是輸入文本,比如當(dāng)我是的抽屜的內(nèi)容是一個表單創(chuàng)建頁面時,我們關(guān)閉抽屜希望表單中用戶輸入的內(nèi)容清空,保證下次進入時用戶能重新創(chuàng)建, 但是實際情況是如果我們不銷毀抽屜里的子組件, 子組件內(nèi)容不會清空,用戶下次打開時開始之前的輸入,這明顯不合理. 如下圖所示:
要想清除緩存,首先就要要內(nèi)部組件重新渲染,所以我們可以通過一個state來控制,如果用戶明確指定了關(guān)閉時要銷毀組件,那么我們就更新這個state,從而這個子元素也就不會有緩存了.具體實現(xiàn)如下:
function Drawer(props) { // ... let [isDesChild, setIsDesChild] = useState(false) const handleClose = () => { // ... if(destroyOnClose) { setIsDesChild(true) } } useEffect(() => { // ... setIsDesChild(false) }, [props.visible]) const childDom = ( <div className={styles.xDrawerWrap}> <div className={styles.xDrawerContent} { isDesChild ? null : children } </div> </div> ) return childDom }
上述代碼中我們省略了部分不相關(guān)代碼, 主要來關(guān)注isDesChild和setIsDesChild, 這個屬性用來根據(jù)用戶傳入的destroyOnClose屬性倆判斷是否該更新這個state, 如果destroyOnClose為true,說明要更新,那么此時當(dāng)用戶點擊關(guān)閉按鈕的時候, 組件將重新渲染, 在用戶再次點開抽屜時, 我們根據(jù)props.visible的變化,來重新讓子組件渲染出來,這樣就實現(xiàn)了組件卸載的完整流程.
getContainer主要用來控制抽屜組件的渲染位置,默認(rèn)會渲染到body下, 為了提供更靈活的配置,我們需要讓抽屜可以渲染到任何元素下,這樣又怎么實現(xiàn)呢? 這塊實現(xiàn)我們可以采用React Portals來實現(xiàn),具體api介紹如下:
Portal 提供了一種將子節(jié)點渲染到存在于父組件以外的 DOM 節(jié)點的優(yōu)秀的方案。第一個參數(shù)(child)是任何可渲染的 React 子元素,例如一個元素,字符串或 fragment。第二個參數(shù)(container)是一個 DOM 元素。
具體使用如下:
render() { // `domNode` 是一個可以在任何位置的有效 DOM 節(jié)點。 return ReactDOM.createPortal( this.props.children, domNode ); }
所以基于這個api我們就能把抽屜渲染到任何元素下了, 具體實現(xiàn)如下:
const childDom = ( <div className={styles.xDrawerWrap} style={{ position: getContainer === false ? 'absolute' : 'fixed', width: visible ? '100%' : '0', zIndex }} > { !!mask && <div className={styles.xDrawerMask} onClick={maskClosable ? handleClose : null}></div> } <div className={styles.xDrawerContent} style={{ width, [placement]: visible ? 0 : '-100%', ...drawerStyle }}> { isDesChild ? null : children } { !!closable && <span className={styles.xCloseBtn} onClick={handleClose}>X</span> } </div> </div> ) return getContainer === false ? childDom : ReactDOM.createPortal(childDom, getContainer)
因為這里getContainer要支持3種情況,一種是用戶不配置屬性,那么默認(rèn)就掛載到body下,還有就是用戶傳的值為false, 那么就為最近的父元素, 他如果傳一個dom元素,那么將掛載到該元素下,所以以上代碼我們會分情況考慮,還有一點要注意,當(dāng)抽屜打開時,我們要讓父元素溢出隱藏,不讓其滾動,所以我們在這里要設(shè)置一下:
useEffect(() => { setVisible(() => { if(getContainer !== false && props.visible) { getContainer.style.overflow = 'hidden' } return props.visible }) setIsDesChild(false) }, [props.visible, getContainer])
當(dāng)關(guān)閉時恢復(fù)邏輯父級的overflow, 避免影響外部樣式:
const handleClose = () => { onClose && onClose() setVisible((prev) => { if(getContainer !== false && prev) { getContainer.style.overflow = 'auto' } return false }) if(destroyOnClose) { setIsDesChild(true) } }
placement主要用來控制抽屜的彈出方向, 可以從左彈出,也可以從右彈出, 實現(xiàn)過程也比較簡單,我們主要要更具屬性動態(tài)修改定位屬性即可,這里我們會用到es新版的新特性,對象的變量屬性. 核心代碼如下:
<div className={styles.xDrawerContent} style={{ width, [placement]: visible ? 0 : '-100%', ...drawerStyle }}> </div>
這樣,無論是上下左右,都可以完美實現(xiàn)了.
import PropTypes from 'prop-types' // ... Drawer.propTypes = { visible: PropTypes.bool, closable: PropTypes.bool, destroyOnClose: PropTypes.bool, getContainer: PropTypes.element, maskClosable: PropTypes.bool, mask: PropTypes.bool, drawerStyle: PropTypes.object, width: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]), zIndex: PropTypes.number, placement: PropTypes.string, onClose: PropTypes.func }
關(guān)于prop-types的使用官網(wǎng)上有很詳細的案例,這里說一點就是oneOfType的用法, 它用來支持一個組件可能是多種類型中的一個. 組件相關(guān)css代碼如下:
.xDrawerWrap { top: 0; height: 100vh; overflow: hidden; .xDrawerMask { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background-color: rgba(0, 0, 0, .5); } .xDrawerContent { position: absolute; top: 0; padding: 16px; height: 100%; transition: all .3s; background-color: #fff; box-shadow: 0 0 20px rgba(0,0,0, .2); .xCloseBtn { position: absolute; top: 10px; right: 10px; color: #ccc; cursor: pointer; } } }
“如何使用React Portals實現(xiàn)一個功能強大的抽屜組件”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
網(wǎng)站欄目:如何使用ReactPortals實現(xiàn)一個功能強大的抽屜組件
URL網(wǎng)址:http://muchs.cn/article12/jiocdc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開發(fā)、動態(tài)網(wǎng)站、Google、自適應(yīng)網(wǎng)站、手機網(wǎng)站建設(shè)、App開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)