自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

配合React Portals實(shí)現(xiàn)一個(gè)功能強(qiáng)大的抽屜(Drawer)組件

開發(fā) 前端
對(duì)于react選手來說,如果沒用typescript,建議大家都用PropTypes, 它是react內(nèi)置的類型檢測(cè)工具,我們可以直接在項(xiàng)目中導(dǎo)入. vue有自帶的屬性檢測(cè)方式,這里就不一一介紹了.

[[429338]]

正文

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

1. 組件設(shè)計(jì)思路

按照之前筆者總結(jié)的組件設(shè)計(jì)原則,我們第一步是要確認(rèn)需求. 一個(gè)抽屜(Drawer)組件會(huì)有如下需求點(diǎn):

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

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

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

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

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

2.1. Drawer組件框架設(shè)計(jì)

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

  1. import PropTypes from 'prop-types' 
  2. import styles from './index.less' 
  3.  
  4. /** 
  5.  * Drawer 抽屜組件 
  6.  * @param {visible} bool 抽屜是否可見 
  7.  * @param {closable} bool 是否顯示右上角的關(guān)閉按鈕 
  8.  * @param {destroyOnClose} bool 關(guān)閉時(shí)銷毀里面的子元素 
  9.  * @param {getContainer} HTMLElement 指定 Drawer 掛載的 HTML 節(jié)點(diǎn), false 為掛載在當(dāng)前 dom 
  10.  * @param {maskClosable} bool 點(diǎn)擊蒙層是否允許關(guān)閉抽屜 
  11.  * @param {mask} bool 是否展示遮罩 
  12.  * @param {drawerStyle} object 用來設(shè)置抽屜彈出層樣式 
  13.  * @param {width} number|string 彈出層寬度 
  14.  * @param {zIndex} number 彈出層層級(jí) 
  15.  * @param {placement} string 抽屜方向 
  16.  * @param {onClose} string 點(diǎn)擊關(guān)閉時(shí)的回調(diào) 
  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.     <div className={styles.xDrawerWrap}> 
  35.       <div className={styles.xDrawerMask} ></div> 
  36.       <div  
  37.         className={styles.xDrawerContent} 
  38.         { 
  39.           children 
  40.         } 
  41.         { 
  42.           !!closable && <span className={styles.xCloseBtn}>X</span> 
  43.         } 
  44.       </div> 
  45.     </div> 
  46.   ) 
  47.   return childDom 
  48.  
  49. export default Drawer 

有了這個(gè)框架,我們來一步步往里面實(shí)現(xiàn)內(nèi)容吧.

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

之所以要先實(shí)現(xiàn)這幾個(gè)功能,是因?yàn)樗麄儗?shí)現(xiàn)都比較簡(jiǎn)單,不會(huì)牽扯到其他復(fù)雜邏輯.只需要對(duì)外暴露屬性并使用屬性即可. 具體實(shí)現(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.     <div  
  29.       className={styles.xDrawerWrap} 
  30.       style={{ 
  31.         width: visible ? '100%' : '0'
  32.         zIndex 
  33.       }} 
  34.     > 
  35.       { !!mask && <div className={styles.xDrawerMask} onClick={maskClosable ? handleClose : null}></div> } 
  36.       <div  
  37.         className={styles.xDrawerContent} 
  38.         style={{ 
  39.           width, 
  40.           ...drawerStyle 
  41.         }}> 
  42.         { children } 
  43.         { 
  44.           !!closable && <span className={styles.xCloseBtn} onClick={handleClose}>X</span> 
  45.         } 
  46.       </div> 
  47.     </div> 
  48.   ) 
  49.   return childDom 

上述實(shí)現(xiàn)過程值得注意的就是我們組件設(shè)計(jì)采用了react hooks技術(shù), 在這里用到了useState, useEffect, 如果大家不懂的可以去官網(wǎng)學(xué)習(xí), 非常簡(jiǎn)單,如果有不懂的可以和筆者交流或者在評(píng)論區(qū)提問. 抽屜動(dòng)畫我們通過控制抽屜內(nèi)容的寬度來實(shí)現(xiàn),配合overflow:hidden, 后面我會(huì)單獨(dú)附上css代碼供大家參考.

2.3 實(shí)現(xiàn)destroyOnClose

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

要想清除緩存,首先就要要內(nèi)部組件重新渲染,所以我們可以通過一個(gè)state來控制,如果用戶明確指定了關(guān)閉時(shí)要銷毀組件,那么我們就更新這個(gè)state,從而這個(gè)子元素也就不會(huì)有緩存了.具體實(shí)現(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.     <div className={styles.xDrawerWrap}> 
  19.       <div className={styles.xDrawerContent} 
  20.         { 
  21.           isDesChild ? null : children 
  22.         } 
  23.       </div> 
  24.     </div> 
  25.   ) 
  26.   return childDom 

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

2.4 實(shí)現(xiàn)getContainer

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

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

具體使用如下:

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

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

  1. const childDom = ( 
  2.     <div  
  3.       className={styles.xDrawerWrap} 
  4.       style={{ 
  5.         position: getContainer === false ? 'absolute' : 'fixed'
  6.         width: visible ? '100%' : '0'
  7.         zIndex 
  8.       }} 
  9.     > 
  10.       { !!mask && <div className={styles.xDrawerMask} onClick={maskClosable ? handleClose : null}></div> } 
  11.       <div  
  12.         className={styles.xDrawerContent} 
  13.         style={{ 
  14.           width, 
  15.           [placement]: visible ? 0 : '-100%'
  16.           ...drawerStyle 
  17.         }}> 
  18.         { 
  19.           isDesChild ? null : children 
  20.         } 
  21.         { 
  22.           !!closable && <span className={styles.xCloseBtn} onClick={handleClose}>X</span> 
  23.         } 
  24.       </div> 
  25.     </div> 
  26.   ) 
  27.  
  28.   return getContainer === false ? childDom 
  29.             : ReactDOM.createPortal(childDom, getContainer) 

因?yàn)檫@里getContainer要支持3種情況,一種是用戶不配置屬性,那么默認(rèn)就掛載到body下,還有就是用戶傳的值為false, 那么就為最近的父元素, 他如果傳一個(gè)dom元素,那么將掛載到該元素下,所以以上代碼我們會(huì)分情況考慮,還有一點(diǎn)要注意,當(dāng)抽屜打開時(shí),我們要讓父元素溢出隱藏,不讓其滾動(dòng),所以我們?cè)谶@里要設(shè)置一下:

  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]) 

當(dāng)關(guān)閉時(shí)恢復(fù)邏輯父級(jí)的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 實(shí)現(xiàn)placement

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

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

 

 

 

這樣,無論是上下左右,都可以完美實(shí)現(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 

關(guān)于prop-types的使用官網(wǎng)上有很詳細(xì)的案例,這里說一點(diǎn)就是oneOfType的用法, 它用來支持一個(gè)組件可能是多種類型中的一個(gè). 組件相關(guān)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.   } 

通過以上步驟, 一個(gè)功能強(qiáng)大的的drawer組件就完成了,關(guān)于代碼中的css module和classnames的使用大家可以自己去官網(wǎng)學(xué)習(xí),非常簡(jiǎn)單.如果不懂的可以在評(píng)論區(qū)提問,筆者看到后會(huì)第一時(shí)間解答.

最后

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

  • modal(模態(tài)窗),
  • alert(警告提示),
  • badge(徽標(biāo)),
  • table(表格),
  • tooltip(工具提示條),
  • Skeleton(骨架屏),
  • Message(全局提示),
  • form(form表單),
  • switch(開關(guān)),
  • 日期/日歷,
  • 二維碼識(shí)別器組件

 

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

 

責(zé)任編輯:武曉燕 來源: 趣談前端
相關(guān)推薦

2021-03-31 08:01:24

React Portareactcss3

2023-08-29 17:43:39

人工智能Fooocus

2022-03-10 09:00:37

提醒框ReactVue

2011-02-23 13:52:07

vsftpd

2023-09-21 15:10:55

2017-02-09 18:01:22

Android圖片選擇器開發(fā)

2010-03-05 09:19:54

Android系統(tǒng)

2013-01-23 11:30:39

JSVirtualKeybjQuery

2017-04-20 18:00:59

Linux命令行工具系統(tǒng)信息

2021-07-09 10:14:05

IP工具命令

2021-06-21 15:49:39

React動(dòng)效組件

2022-02-24 13:08:12

前端開發(fā)視頻

2010-02-24 17:58:28

Python 測(cè)試框架

2022-10-10 10:14:38

Python繪圖庫

2009-07-07 08:46:11

微軟Windows 7新功能

2020-12-15 07:54:40

工具Hutoolgithub

2020-12-15 15:08:17

工具Java線程

2024-01-15 18:02:09

docker系統(tǒng)文件格式

2021-10-10 12:17:06

Weakpass在線字典生成器安全工具

2021-11-01 05:53:08

Doldrums逆向工程分析工具安全工具
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)