溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何使用React Portals實現一個功能強大的抽屜組件

發布時間:2021-10-15 09:44:06 來源:億速云 閱讀:162 作者:iii 欄目:web開發

本篇內容介紹了“如何使用React Portals實現一個功能強大的抽屜組件”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

正文

在開始組件設計之前希望大家對css3和js有一定的基礎,并了解基本的react/vue語法.我們先看看實現后的組件效果:

如何使用React Portals實現一個功能強大的抽屜組件

1. 組件設計思路

按照之前筆者總結的組件設計原則,我們第一步是要確認需求. 一個抽屜(Drawer)組件會有如下需求點:

  • 能控制抽屜是否可見

  • 能手動配置抽屜的關閉按鈕

  • 能控制抽屜的打開方向

  • 關閉抽屜時是否銷毀里面的子元素(這個問題是工作中頻繁遇到的問題)

  • 指定 Drawer 掛載的 HTML 節點, 可以將抽屜掛載在任何元素上

  • 點擊蒙層可以控制是否允許關閉抽屜

  • 能控制遮罩層的展示

  • 能自定義抽屜彈出層樣式

  • 可以設置抽屜彈出層寬度

  • 能控制彈出層層級

  • 能控制抽屜彈出方向(上下左右)

  • 點擊關閉按鈕時能提供回調供開發者進行相關操作

需求收集好之后,作為一個有追求的程序員, 會得出如下線框圖:

如何使用React Portals實現一個功能強大的抽屜組件

對于react選手來說,如果沒用typescript,建議大家都用PropTypes, 它是react內置的類型檢測工具,我們可以直接在項目中導入.  vue有自帶的屬性檢測方式,這里就不一一介紹了.

通過以上需求分析, 是不是覺得一個抽屜組件要實現這么多功能很復雜呢?  確實有點復雜,但是不要怕,有了上面精確的需求分析,我們只需要一步步按照功能點實現就好了.對于我們常用的table組件,  modal組件等其實也需要考慮到很多使用場景和功能點, 比如antd的table組件暴露了幾十個屬性,如果不好好理清具體的需求,  實現這樣的組件是非常麻煩的.接下來我們就來看看具體實現.

2. 基于react實現一個Drawer組件

2.1. Drawer組件框架設計

首先我們先根據需求將組件框架寫好,這樣后面寫業務邏輯會更清晰:

import PropTypes from 'prop-types' import styles from './index.less'  /**  * Drawer 抽屜組件  * @param {visible} bool 抽屜是否可見  * @param {closable} bool 是否顯示右上角的關閉按鈕  * @param {destroyOnClose} bool 關閉時銷毀里面的子元素  * @param {getContainer} HTMLElement 指定 Drawer 掛載的 HTML 節點, false 為掛載在當前 dom  * @param {maskClosable} bool 點擊蒙層是否允許關閉抽屜  * @param {mask} bool 是否展示遮罩  * @param {drawerStyle} object 用來設置抽屜彈出層樣式  * @param {width} number|string 彈出層寬度  * @param {zIndex} number 彈出層層級  * @param {placement} string 抽屜方向  * @param {onClose} string 點擊關閉時的回調  */ 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

有了這個框架,我們來一步步往里面實現內容吧.

2.2 實現visible, closable, onClose, mask, maskClosable, width, zIndex,  drawerStyle

之所以要先實現這幾個功能,是因為他們實現都比較簡單,不會牽扯到其他復雜邏輯.只需要對外暴露屬性并使用屬性即可. 具體實現如下:

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 }

上述實現過程值得注意的就是我們組件設計采用了react hooks技術, 在這里用到了useState, useEffect,  如果大家不懂的可以去官網學習, 非常簡單,如果有不懂的可以和筆者交流或者在評論區提問.  抽屜動畫我們通過控制抽屜內容的寬度來實現,配合overflow:hidden, 后面我會單獨附上css代碼供大家參考.

2.3 實現destroyOnClose

destroyOnClose主要是用來清除組件緩存,比較常用的場景就是輸入文本,比如當我是的抽屜的內容是一個表單創建頁面時,我們關閉抽屜希望表單中用戶輸入的內容清空,保證下次進入時用戶能重新創建,  但是實際情況是如果我們不銷毀抽屜里的子組件, 子組件內容不會清空,用戶下次打開時開始之前的輸入,這明顯不合理. 如下圖所示:

 如何使用React Portals實現一個功能強大的抽屜組件

要想清除緩存,首先就要要內部組件重新渲染,所以我們可以通過一個state來控制,如果用戶明確指定了關閉時要銷毀組件,那么我們就更新這個state,從而這個子元素也就不會有緩存了.具體實現如下:

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 }

上述代碼中我們省略了部分不相關代碼, 主要來關注isDesChild和setIsDesChild,  這個屬性用來根據用戶傳入的destroyOnClose屬性倆判斷是否該更新這個state,  如果destroyOnClose為true,說明要更新,那么此時當用戶點擊關閉按鈕的時候, 組件將重新渲染, 在用戶再次點開抽屜時,  我們根據props.visible的變化,來重新讓子組件渲染出來,這樣就實現了組件卸載的完整流程.

2.4 實現getContainer

getContainer主要用來控制抽屜組件的渲染位置,默認會渲染到body下,  為了提供更靈活的配置,我們需要讓抽屜可以渲染到任何元素下,這樣又怎么實現呢? 這塊實現我們可以采用React Portals來實現,具體api介紹如下:

  • Portal 提供了一種將子節點渲染到存在于父組件以外的 DOM 節點的優秀的方案。第一個參數(child)是任何可渲染的 React  子元素,例如一個元素,字符串或 fragment。第二個參數(container)是一個 DOM 元素。

具體使用如下:

render() {   // `domNode` 是一個可以在任何位置的有效 DOM 節點。   return ReactDOM.createPortal(     this.props.children,     domNode   ); }

所以基于這個api我們就能把抽屜渲染到任何元素下了, 具體實現如下:

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種情況,一種是用戶不配置屬性,那么默認就掛載到body下,還有就是用戶傳的值為false, 那么就為最近的父元素,  他如果傳一個dom元素,那么將掛載到該元素下,所以以上代碼我們會分情況考慮,還有一點要注意,當抽屜打開時,我們要讓父元素溢出隱藏,不讓其滾動,所以我們在這里要設置一下:

useEffect(() => {     setVisible(() => {       if(getContainer !== false && props.visible) {         getContainer.style.overflow = 'hidden'       }       return props.visible     })     setIsDesChild(false)   }, [props.visible, getContainer])

當關閉時恢復邏輯父級的overflow, 避免影響外部樣式:

const handleClose = () => {     onClose && onClose()     setVisible((prev) => {       if(getContainer !== false && prev) {         getContainer.style.overflow = 'auto'       }       return false     })     if(destroyOnClose) {       setIsDesChild(true)     }   }

2.5 實現placement

placement主要用來控制抽屜的彈出方向, 可以從左彈出,也可以從右彈出,  實現過程也比較簡單,我們主要要更具屬性動態修改定位屬性即可,這里我們會用到es新版的新特性,對象的變量屬性. 核心代碼如下:

<div    className={styles.xDrawerContent}    style={{     width,     [placement]: visible ? 0 : '-100%',     ...drawerStyle     }}>  </div>

這樣,無論是上下左右,都可以完美實現了.

2.6 健壯性支持, 我們采用react提供的propTypes工具:

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 }

關于prop-types的使用官網上有很詳細的案例,這里說一點就是oneOfType的用法, 它用來支持一個組件可能是多種類型中的一個.  組件相關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實現一個功能強大的抽屜組件”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女