日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網營銷解決方案
配合ReactPortals實現(xiàn)一個功能強大的抽屜(Drawer)組件

[[429338]]

正文

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

1. 組件設計思路

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

  • 能控制抽屜是否可見
  • 能手動配置抽屜的關閉按鈕
  • 能控制抽屜的打開方向
  • 關閉抽屜時是否銷毀里面的子元素(這個問題是工作中頻繁遇到的問題)
  • 指定 Drawer 掛載的 HTML 節(jié)點, 可以將抽屜掛載在任何元素上
  • 點擊蒙層可以控制是否允許關閉抽屜
  • 能控制遮罩層的展示
  • 能自定義抽屜彈出層樣式
  • 可以設置抽屜彈出層寬度
  • 能控制彈出層層級
  • 能控制抽屜彈出方向(上下左右)
  • 點擊關閉按鈕時能提供回調供開發(fā)者進行相關操作

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

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

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

2. 基于react實現(xiàn)一個Drawer組件

2.1. Drawer組件框架設計

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

 
 
 
 
  1. import PropTypes from 'prop-types' 
  2. import styles from './index.less' 
  3.  
  4. /** 
  5.  * Drawer 抽屜組件 
  6.  * @param {visible} bool 抽屜是否可見 
  7.  * @param {closable} bool 是否顯示右上角的關閉按鈕 
  8.  * @param {destroyOnClose} bool 關閉時銷毀里面的子元素 
  9.  * @param {getContainer} HTMLElement 指定 Drawer 掛載的 HTML 節(jié)點, false 為掛載在當前 dom 
  10.  * @param {maskClosable} bool 點擊蒙層是否允許關閉抽屜 
  11.  * @param {mask} bool 是否展示遮罩 
  12.  * @param {drawerStyle} object 用來設置抽屜彈出層樣式 
  13.  * @param {width} number|string 彈出層寬度 
  14.  * @param {zIndex} number 彈出層層級 
  15.  * @param {placement} string 抽屜方向 
  16.  * @param {onClose} string 點擊關閉時的回調 
  17.  */ 
  18. function Drawer(props) { 
  19.   const { 
  20.     closable = true, 
  21.     destroyOnClose, 
  22.     getContainer = document.body, 
  23.     maskClosable = true, 
  24.     mask = true, 
  25.     drawerStyle, 
  26.     width = '300px', 
  27.     zIndex = 10, 
  28.     placement = 'right', 
  29.     onClose, 
  30.     children 
  31.   } = props 
  32.  
  33.   const childDom = ( 
  34.      
  35.       
 
  •       
  •         className={styles.xDrawerContent} 
  •         { 
  •           children 
  •         } 
  •         { 
  •           !!closable && 
  •         } 
  •       
  •  
  •     
  •  
  •   ) 
  •   return childDom 
  •  
  • export default Drawer 
  • 有了這個框架,我們來一步步往里面實現(xiàn)內容吧.

    2.2 實現(xiàn)visible, closable, onClose, mask, maskClosable, width, zIndex, drawerStyle

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

     
     
     
     
    1. function Drawer(props) { 
    2.   const { 
    3.     closable = true, 
    4.     destroyOnClose, 
    5.     getContainer = document.body, 
    6.     maskClosable = true, 
    7.     mask = true, 
    8.     drawerStyle, 
    9.     width = '300px', 
    10.     zIndex = 10, 
    11.     placement = 'right', 
    12.     onClose, 
    13.     children 
    14.   } = props 
    15.  
    16.   let [visible, setVisible] = useState(props.visible) 
    17.  
    18.   const handleClose = () => { 
    19.     setVisible(false) 
    20.     onClose && onClose() 
    21.   } 
    22.  
    23.   useEffect(() => { 
    24.     setVisible(props.visible) 
    25.   }, [props.visible]) 
    26.  
    27.   const childDom = ( 
    28.     
    29.       className={styles.xDrawerWrap} 
    30.       style={{ 
    31.         width: visible ? '100%' : '0', 
    32.         zIndex 
    33.       }} 
    34.     > 
    35.       { !!mask && 
     } 
  •       
  •         className={styles.xDrawerContent} 
  •         style={{ 
  •           width, 
  •           ...drawerStyle 
  •         }}> 
  •         { children } 
  •         { 
  •           !!closable && 
  •         } 
  •       
  •  
  •     
  •  
  •   ) 
  •   return childDom 
  • 上述實現(xiàn)過程值得注意的就是我們組件設計采用了react hooks技術, 在這里用到了useState, useEffect, 如果大家不懂的可以去官網學習, 非常簡單,如果有不懂的可以和筆者交流或者在評論區(qū)提問. 抽屜動畫我們通過控制抽屜內容的寬度來實現(xiàn),配合overflow:hidden, 后面我會單獨附上css代碼供大家參考.

    2.3 實現(xiàn)destroyOnClose

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

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

     
     
     
     
    1. function Drawer(props) { 
    2.   // ... 
    3.   let [isDesChild, setIsDesChild] = useState(false) 
    4.  
    5.   const handleClose = () => { 
    6.     // ... 
    7.     if(destroyOnClose) { 
    8.       setIsDesChild(true) 
    9.     } 
    10.   } 
    11.  
    12.   useEffect(() => { 
    13.     // ... 
    14.     setIsDesChild(false) 
    15.   }, [props.visible]) 
    16.  
    17.   const childDom = ( 
    18.      
    19.       
    20.         { 
    21.           isDesChild ? null : children 
    22.         } 
    23.       
     
  •     
  •  
  •   ) 
  •   return childDom 
  • 上述代碼中我們省略了部分不相關代碼, 主要來關注isDesChild和setIsDesChild, 這個屬性用來根據用戶傳入的destroyOnClose屬性倆判斷是否該更新這個state, 如果destroyOnClose為true,說明要更新,那么此時當用戶點擊關閉按鈕的時候, 組件將重新渲染, 在用戶再次點開抽屜時, 我們根據props.visible的變化,來重新讓子組件渲染出來,這樣就實現(xiàn)了組件卸載的完整流程.

    2.4 實現(xiàn)getContainer

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

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

    具體使用如下:

     
     
     
     
    1. render() { 
    2.   // `domNode` 是一個可以在任何位置的有效 DOM 節(jié)點。 
    3.   return ReactDOM.createPortal( 
    4.     this.props.children, 
    5.     domNode 
    6.   ); 

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

     
     
     
     
    1. const childDom = ( 
    2.     
    3.       className={styles.xDrawerWrap} 
    4.       style={{ 
    5.         position: getContainer === false ? 'absolute' : 'fixed', 
    6.         width: visible ? '100%' : '0', 
    7.         zIndex 
    8.       }} 
    9.     > 
    10.       { !!mask && 
     } 
  •       
  •         className={styles.xDrawerContent} 
  •         style={{ 
  •           width, 
  •           [placement]: visible ? 0 : '-100%', 
  •           ...drawerStyle 
  •         }}> 
  •         { 
  •           isDesChild ? null : children 
  •         } 
  •         { 
  •           !!closable && 
  •         } 
  •       
  •  
  •     
  •  
  •   ) 
  •  
  •   return getContainer === false ? childDom 
  •             : ReactDOM.createPortal(childDom, getContainer) 
  • 因為這里getContainer要支持3種情況,一種是用戶不配置屬性,那么默認就掛載到body下,還有就是用戶傳的值為false, 那么就為最近的父元素, 他如果傳一個dom元素,那么將掛載到該元素下,所以以上代碼我們會分情況考慮,還有一點要注意,當抽屜打開時,我們要讓父元素溢出隱藏,不讓其滾動,所以我們在這里要設置一下:

     
     
     
     
    1. useEffect(() => { 
    2.     setVisible(() => { 
    3.       if(getContainer !== false && props.visible) { 
    4.         getContainer.style.overflow = 'hidden' 
    5.       } 
    6.       return props.visible 
    7.     }) 
    8.     setIsDesChild(false) 
    9.   }, [props.visible, getContainer]) 

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

     
     
     
     
    1. const handleClose = () => { 
    2.     onClose && onClose() 
    3.     setVisible((prev) => { 
    4.       if(getContainer !== false && prev) { 
    5.         getContainer.style.overflow = 'auto' 
    6.       } 
    7.       return false 
    8.     }) 
    9.     if(destroyOnClose) { 
    10.       setIsDesChild(true) 
    11.     } 
    12.   } 

    2.5 實現(xiàn)placement

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

     
     
     
     
    1.   className={styles.xDrawerContent} 
    2.   style={{ 
    3.     width, 
    4.     [placement]: visible ? 0 : '-100%', 
    5.     ...drawerStyle 
    6.     }}> 
    7.  
     

     

     

    這樣,無論是上下左右,都可以完美實現(xiàn)了.

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

     
     
     
     
    1. import PropTypes from 'prop-types' 
    2. // ... 
    3. Drawer.propTypes = { 
    4.   visible: PropTypes.bool, 
    5.   closable: PropTypes.bool, 
    6.   destroyOnClose: PropTypes.bool, 
    7.   getContainer: PropTypes.element, 
    8.   maskClosable: PropTypes.bool, 
    9.   mask: PropTypes.bool, 
    10.   drawerStyle: PropTypes.object, 
    11.   width: PropTypes.oneOfType([ 
    12.     PropTypes.string, 
    13.     PropTypes.number 
    14.   ]), 
    15.   zIndex: PropTypes.number, 
    16.   placement: PropTypes.string, 
    17.   onClose: PropTypes.func 

    關于prop-types的使用官網上有很詳細的案例,這里說一點就是oneOfType的用法, 它用來支持一個組件可能是多種類型中的一個. 組件相關css代碼如下:

     
     
     
     
    1. .xDrawerWrap { 
    2.   top: 0; 
    3.   height: 100vh; 
    4.   overflow: hidden; 
    5.   .xDrawerMask { 
    6.     position: absolute; 
    7.     left: 0; 
    8.     right: 0; 
    9.     top: 0; 
    10.     bottom: 0; 
    11.     background-color: rgba(0, 0, 0, .5); 
    12.   } 
    13.   .xDrawerContent { 
    14.     position: absolute; 
    15.     top: 0; 
    16.     padding: 16px; 
    17.     height: 100%; 
    18.     transition: all .3s; 
    19.     background-color: #fff; 
    20.     box-shadow: 0 0 20px rgba(0,0,0, .2); 
    21.     .xCloseBtn { 
    22.       position: absolute; 
    23.       top: 10px; 
    24.       right: 10px; 
    25.       color: #ccc; 
    26.       cursor: pointer; 
    27.     } 
    28.   } 

    通過以上步驟, 一個功能強大的的drawer組件就完成了,關于代碼中的css module和classnames的使用大家可以自己去官網學習,非常簡單.如果不懂的可以在評論區(qū)提問,筆者看到后會第一時間解答.

    最后

    后續(xù)筆者將會繼續(xù)實現(xiàn)

     

    等組件, 來復盤筆者多年的組件化之旅。


    當前題目:配合ReactPortals實現(xiàn)一個功能強大的抽屜(Drawer)組件
    標題URL:http://www.5511xx.com/article/cdojhds.html

    其他資訊