此文前端框架使用 rax,全篇代碼暫未開源(待開源) 原文連接地址:Nealyang/PersonalBlog前端
貌似在面試中,你若是設計一個 react/vue 組件,貌似已是司空見慣的問題了。本文不是理論片,更多的是本身的一步步思考和實踐。文中會有不少筆者的思考過程,歡迎評論區多多交流和討論。vue
從需求討論、技術方案探討到編碼、到最終的測試,經歷過了不少次的腦暴,也遇到過很是多的坑,其中有可能跟業務有關、也有可能跟框架有關,基於這些坑,又討論了不少解決方案和很是 hack(歪門邪道)的對策。可是隨着時間的推移,再回頭看看當時的 hack 代碼,不少都不太記得爲何這麼寫了,因此這裏簡單記錄下,Filter 組件的開發過程。以便後面查詢,更但願能你們一塊兒探討,以求得更優質的代碼架構和實現思路。node
因爲代碼編寫使用基於底層 weex 的 rax 框架,因此有些坑,或許對於正在使用 react 或者 vue 的你並不會遇到,能夠直接忽略react
Filter,已常常見的不可再常見的組件了,顧名思義,就是個篩選過濾器。咱們先看看現有 app 上的一些 filter 展示 形式。既然作組件,咱們就須要它足夠的通用,足夠的易於擴展。git
在說 Filter 的業務特徵以前,咱們先約束下每一部分的命名,以便於你更好的閱讀此文:github
上面分別是拍賣和飛豬的 filter 頁面,從這兩個頁面中,咱們大概能夠總結出關於 Filter 的一下幾點業務畫像:面試
因爲 rax 1.0 ts+hooks 開源版本還在開發中,因此倉庫連接暫時就不放上了docker
效果圖:redux
console
處可見拋出的查詢參數數組
src ├─ Filter.js //Filter 最外層父容器 ├─ constant.js //項目代碼常量定義 ├─ index.js //入口文件 ├─ navbar // navBar 文件夾 │ ├─ NavBase.js //navBar 基類 NavQuickSearch 和 NavRelatePanel 父類 │ ├─ NavQuickSearch.js // 快速搜索(無 panel)的 navBar │ ├─ NavRelatePanel.js // 帶有 panel 的 navBar │ └─ index.js // 導出文件 ├─ panel │ └─ index.js // panel 面板組件代碼 └─ style.js
RelatePanel
:篩選項關聯Panel型,即篩選頭和 Panel 是一對一關係,點擊篩選頭展現 PanelQuickSearch
:篩選項快速搜索排序型,即篩選頭沒有對應 Panel,點擊篩選頭直接觸發搜索PureUI
:純 UI佔位類型,即純 UI 放置,不涉及搜索,好比訂閱按鈕場景onChange(params)
回調函數來觸發rax-pui-list-select
,列表選擇業務面板rax-pui-location-select
,省市區級聯選擇業務面板rax-pui-multi-selection-panel
,多選業務面板,查看組件使用文檔這裏指的是 Filter 的功能 Feature,跟上文說起的 Filter 組件功能可能並不能徹底覆蓋,可是咱們提供解決方案,組件的設計始終秉持着不侵入業務的原則,全部與業務相關均給予配置入口。
import Filter from 'rax-pui-filter'; render( <Filter navConfig={[]} onChange={()=>{}}> <Filter.Panel> <業務組件1 /> </Filter.Panel> <Filter.Panel> <業務組件2 /> </Filter.Panel> </Filter> );
何爲業務功能何爲組件功能,這個須要具體的探討,其實也沒有嚴格意義上的區分。說白了,就是你買個手機,他都會送你充電器。可是。。。爲何不少手機也送手機殼(小米、華爲、榮耀)可是 iPhone 卻不送呢?因此究竟是不是標配?
對於咱們這個組件,簡而言之:咱們能作到的,咱們都作!可是其中咱們仍是梳理出某些功能仍是數據業務功能:
換言之,Filter 裏面任何功能均可以說爲業務功能。可是咱們須要提供 80%業務都須要的功能封裝做爲 Filter 的 Future。這就是咱們的目的。
根據上面的業務功能和組件功能的區分,咱們就知道在使用 Filter 的時候,你應該給我傳遞什麼配置,以及什麼方法。
參數 | 說明 | 類型 | 默認值(是否必填) |
---|---|---|---|
navConfig | 篩選頭配置, <a href='#navConfig'>點擊查看詳細配置項 </a> <br /><br />效果圖<br /> ![]() |
Array<Object> | - (必填) |
offsetTop | Filter組件展開面板狀態下距離頁面頂部的高度,有兩種狀態:固定位置和跟隨頁面滾動吸附置頂<br /><br /> 固定位置 狀態下距離頁面頂部的高度<br /> 跟隨頁面滾動吸附置頂: 狀態下距離頁面頂部的高度 <br /><br />效果圖<br /> ![]() |
Number | 0 |
styles | 配置樣式,Filter中全部樣式均可使用styles 集合對象來配置覆蓋<br />styles 格式<br />![]() |
Object | {} |
getStickyRef | 獲取 Sticky 節點的 ref 實例,用於滾動吸附場景,內部配合 pm-app-plus 容器組件點擊 Filter 時自動吸附置頂<br /><br />示例圖<br />![]() |
Function | |
keepHighlight | 篩選條件改變後是否須要在篩選頭保持高亮<br /><br />效果圖<br />![]() |
Boolean | false |
clickMaskClosable | 開啓 mask 背景的點擊隱藏 | Boolean | true |
onChange | Filter 搜索變動回調函數 <br /> 簽名: Function(params:Object,index:Number, urlQuery: Object) => void <br /> 參數: <br />params: Object 搜索參數<br />index:Number 觸發搜索的 Panel 搜索<br />urlQuery:Object URL query 對象<br /> |
Function | |
onPanelVisibleChange | Panel 顯示隱藏回調函數 <br /> 簽名: Function({ visible:Boolean, triggerIndex:Number, triggerType:String }) => void <br /> 參數: <br /> visible:Boolean 顯示隱藏標誌量 <br /> triggerIndex:Number 觸發的篩選項索引值 <br />triggerType:String 觸發類型 <br /> <br /><br />triggerType詳解 包含三種觸發類型<br />Navbar :來自篩選頭的點擊觸發<br />Mask :來自背景層的點擊觸發<br />Panel :來自Panel 的 onChange 回調觸發 |
Function |
篩選項類型 type
RelatePanel
:篩選項關聯Panel型,即篩選頭和 Panel 是一對一關係,點擊篩選頭展現 PanelQuickSearch
:篩選項快速搜索排序型,即篩選頭沒有對應 Panel,點擊篩選頭直接觸發搜索PureUI
:純 UI佔位類型,即純 UI 放置,不涉及搜索,好比訂閱按鈕場景注意 若是 navConfig 內置的UI參數不知足您的需求,請使用renderItem
自定義渲染函數來控制篩選頭 UI
參數 | 說明 | 類型 | 默認值(是否必填) |
---|---|---|---|
type | 篩選項類型 <br /><br /> 三種類型<br/>RelatePanel : 篩選項關聯數據面板類型<br/>QuickSearch : 篩選項快速搜索排序類型<br/>PureUI : 純 UI佔位類型 |
String | 'RelatePanel' |
text <br /><br /><br /> 注意 RelatePanel 類型生效 |
篩選頭顯示文案 <br /> 文字溢出用... 展現 |
String | - (必填) |
icons <br /><br /><br /> 注意 RelatePanel 類型生效 |
篩選頭 icon:normal 正常態 和 active 激活態 圖標 <br /> 數據格式 <br /> Object 類型 :<br /> ![]() String 類型 : <br />![]() ![]() |
Object or String | - |
options <br /><br /><br /> 注意 QuickSearch 類型生效 |
快速搜索排序類型的數據源 <br />數據格式<br /> ![]() |
Array | (必填) |
optionsIndex <br /><br /><br /> 注意 QuickSearch 類型生效 |
快速搜索排序類型默認選中的索引 | String | 0 |
optionsKey <br /><br /><br /> 注意 QuickSearch 類型生效 |
指定快速搜索排序對應的搜索 key,用到 onChange 回調中 | String | 不提供默認使用當前篩選項的索引 |
formatText | 文案格式化函數<br /> 簽名:Function(text:String) => text <br /> 參數: <br />text: String 篩選頭文案 |
Function | (text)=>text |
disabled | 禁用篩選頭點擊 | Boolean | true |
hasSeperator | 是否展現右側分隔符<br /><br />效果圖<br /> ![]() |
Boolean | false |
hasPanel | 當前篩選頭是否有對應的 panel | Boolean | true |
renderItem | 自定義渲染<br /> 注意 <br /> 提供的配置項沒法知足你的 UI 需求時使用<br /> 簽名:Function(isActive:Boolean, this:Element) => Element <br /> 參數: <br />isActive:Boolean 篩選頭是否爲激活狀態<br />this:Element 篩選頭this實例 |
Function | - |
animation | 動畫配置,採用內置的動畫 <br />參數說明 <br />![]() rotate 動畫類型 |
Object | |
animationHook | 用戶自定義動畫的鉤子函數,內置動畫沒法知足需求時使用 <br /> 簽名:Function(refImg:Element, isActive:Boolean) => text <br /> 參數: <br />refImg:Element 篩選頭圖標的 ref 實例 <br />isActive:Boolean 篩選頭是否爲激活狀態 |
Function | - |
參數 | 說明 | 類型 | 默認值(是否必填) |
---|---|---|---|
styles | 配置樣式<br />Filter中全部樣式均可使用styles 集合對象來配置覆蓋 |
Object | {} |
displayMode | Panel 展示形式:全屏、下拉 <br />參數說明 <br />全屏:Fullscreen <br />下拉:Dropdown |
String | 'Dropdown' |
noAnimation | 禁止動畫 | Boolean | true |
highPerformance | 內部經過 Panel 的顯示隱藏控制 panel 的 render 次數,避免沒必要要的 render,高性能模式下,只會在 Panel 展現 或者 展現隱藏狀態變化時纔會從新 render | Boolean | true |
animation | Panel 展現動畫配置,內置上下左右動畫 <br />參數說明<br /> ![]() direction 控制動畫方向,分別有 up 、down 、left 、right |
Object |
navConfig: [ { type: 'RelatePanel', // type能夠不提供,默認值爲'RelatePanel' text: '向下', // 配置篩選頭文案 icons: { // 配置 icon,分爲正常形態和點擊選中形態 normal: '//gw.alicdn.com/tfs/TB1a7BSeY9YBuNjy0FgXXcxcXXa-27-30.png', active: '//gw.alicdn.com/tfs/TB1NDpme9CWBuNjy0FhXXb6EVXa-27-30.png', }, hasSeperator: true, // 展現豎線分隔符 formatText: text => text + '↓', // 篩選文案的格式化函數 }, { type: 'QuickSearch', optionsIndex: 0, optionsKey: 'price', options: [ // 快速排序列表 { text: '價格', icon: '', value: '0', }, { text: '升序', icon: '//gw.alicdn.com/tfs/TB1PuVHXeL2gK0jSZFmXXc7iXXa-20-20.png', value: '1', }, { text: '降序', icon: '//gw.alicdn.com/tfs/TB1a7BSeY9YBuNjy0FgXXcxcXXa-27-30.png', value: '2', }, ], }, { type: 'RelatePanel', // type能夠不提供,默認值爲'RelatePanel' text: '旋轉', icons: { // 配置 icon,分爲正常形態和點擊選中形態 normal: '//gw.alicdn.com/tfs/TB1PuVHXeL2gK0jSZFmXXc7iXXa-20-20.png', active: '//gw.alicdn.com/tfs/TB1l4lIXhv1gK0jSZFFXXb0sXXa-20-20.png', }, animation: { type: 'rotate' }, // 配置動畫點擊後旋轉圖片,默認沒有動畫 }, { type: 'RelatePanel', // type能夠不提供,默認值爲'RelatePanel' text: '向左', }, { type: 'PureUI', text: '訂閱', renderItem: () => { // 渲染自定義的 UI return ( <Image style={{ width: 120, height: 92, }} source={{ uri: 'https://gw.alicdn.com/tfs/TB1eubQakL0gK0jSZFAXXcA9pXa-60-45.png' }} /> ); }, }, ] // ... <Filter offsetTop={100} // offsetTop = RecycleView上面的組件的高度,當前爲 100 navConfig={this.state.navConfig} // Filter Navbar 配置項 keepHighlight={true} // 保持變動的高亮 styles={styles} // 配置覆蓋內置樣式,大樣式對象集合 onChange={this.handleSearchChange} // Panel 面板顯示隱藏變動事件 onPanelVisibleChange={this.handlePanelVisibleChange}> <Panel highPerformance={true}> <ListSelect {...this.state.data1} /> </Panel> <Panel> <LocationSelect {...this.state.data2} /> </Panel> <Panel displayMode={'Fullscreen'} // 配置 Panel 全屏展現,默認爲下拉展現 animation={{ // 動畫配置 timingFunction: 'cubic-bezier(0.22, 0.61, 0.36, 1)', duration: 200, direction: 'left', // 動畫方向:從右往左方向滑出 }}> <MultiSelect {...this.state.data3} /> </Panel> </Filter>
代碼運行效果圖如上截圖。下面,簡單說下代碼的實現。
開源版本(Ts+hooks+lerna)還未公佈,因此目前仍是採用 rax 0.x 的版本編寫的代碼。這裏只作,有坑的地方代碼處理講解。歡迎各位大佬評論留出各位想法
先從 render 方法看起
render() { const { style = {}, styles = {}, navConfig, keepHighlight } = this.props; const { windowHeight, activeIndex } = this.state; if (!windowHeight) return null; return ( <View style={[defaultStyle.container, styles.container, style]}> {this.renderPanels()} <Navbar ref={r => { this.refNavbar = r; }} navConfig={navConfig} styles={styles} keepHighlight={keepHighlight} activeIndex={activeIndex} onNavbarPress={this.handleNavbarPress} onChange={this.handleSearchChange} /> </View> ); }
獲取一些基本配置,以及 windowHeight(屏幕高度)和 activeIndex(當前第幾個item 處於 active 狀態(被點開))。
之因此咱們的 renderPanels
寫在 NavBar
上面,是由於在 weex 中,zIndex 是不生效的。若想 A 元素在 B 元素上面,則 render 的時候,A 必須在 B 後面。這樣寫是爲了 panel 面板展開的下拉動畫,看起來是從 navBar 下面出來的。
renderPanel 方法就是渲染對應的 panel
/** * 渲染 Panel */ renderPanels = () => { const { activeIndex, windowHeight } = this.state; let { children } = this.props; if (!Array.isArray(children)) { children = [children]; } let index = 0; return children.map(child => { let panelChild = null; let hasPanel = this.panelIndexes[index]; if (!hasPanel) { index++; } if (!this.panelManager[index]) { this.panelManager[index] = {}; } let injectProps = { index, visible: activeIndex === index, windowHeight, filterBarHeight: this.filterBarHeight, maxHeight: this.filterPanelMaxHeight, shouldInitialRender: this.panelManager[index].shouldInitialRender, onChange: this.handleSearchChange.bind(this, index), onNavTextChange: this.handleNavTextChange.bind(this, index), onHidePanel: this.setPanelVisible.bind(this, false, index), onMaskClick: this.handleMaskClick, disableNavbarClick: this.disableNavbarClick, }; if (child.type !== Panel) { panelChild = <Panel {...injectProps}>{child}</Panel>; } else { panelChild = cloneElement(child, injectProps); } index++; return panelChild; }); };
準確的說,這是一個 HOC,咱們將代理、翻譯傳給 Filter 的影響或者 panel 面板須要使用的 props 傳遞給 Panel 面板。好比 onChange 回調,或者面板隱藏的回調以及當前哪個 panel 須要展開等。
因爲 Panel 的面板複雜度咱們未知。爲了不不斷的展開和收齊沒必要要的 render,咱們採用 transform
的方式,將面板不須要顯示的面板移除屏幕外,須要展現的在移入到屏幕內部。具體可見 Panel 的render return
return ( <View ref={r => { this.refPanelContainer = r; }} style={[ defaultStyle.panel, styles.panel, this.panelContainerStyle, { transform: `translateX(-${this.containerTransformDes})`, opacity: 0, }, ]}> <View ref="mask" style={[ defaultStyle.mask, styles.mask, showStyle, isWeb ? { top: 0, zIndex: -1 } : { top: 0 }, ]} onClick={this.handleMaskClick} onTouchMove={this.handleMaskTouchMove} /> {cloneElement(child, injectProps)} </View> );
注意: Panel 面板的坑遠不止這些,好比,咱們都知道,render 是最消耗頁面性能的,而頁面初始化進來,面板名沒有展現出來(此時面板 Panel 在屏幕外),那麼是否須要走 Panel 面板的 render 呢?可是目前的這種寫法,Panel 組件的生命週期是會都走到的。可是若是遇到 Panel 裏面須要請求數據,而後頁面 url 裏查詢參數有 locationId=123
,navItem 須要展現對應的地理位置.若是不渲染 Panel 如何根據 id 拿到對應的地名傳遞給 navItem 去展現?對,咱們能夠攔截 Panel 面板的 render 方法,讓 Panel render null,而後別的生命週期照樣運行。可是,若是 render 中用戶有對 ref
的使用,那麼就可能會形成難以排查的 bug。
因此最終,爲了提升頁面的可交互率可是又不影響頁面需求的狀況下,咱們提供了一個可選的工具:Performance HOC 。 注意,是可選。
export default function performance(Comp) { return class Performance extends Comp { static displayName = `Performance(${Comp.displayName})`; render() { const { shouldInitialRender } = this.props.panelAttributes; if (shouldInitialRender) { return super.render(); } else { return <View />; } } }; }
經過配置Panel 的 shouldInitialRender 屬性來告訴我,是否第一次進來,攔截 render。
固然,Panel 也有不少別的坑,好比,如今 Panel 爲了重複 render,將 Panel 移除屏幕外,那麼,動畫從上而下展開設置初始動畫閃屏如何處理?
Filter 的代碼就是初始化、format、檢查校驗各類傳參,以及 Panel 和 NavBar 通訊中轉 好比 format、好比 handleNavbarPress
從架構圖中大概能夠看出,NavBar 中經過不一樣的配置,展現不一樣的 NavBarItem 的類型,NavQuickSearch
,NavRelatePanel
這裏須要注意的是: NavBar 的數據是經過 Filter
props 傳入的,若是狀態放到 Filter 也就是 NavBar 的父組件管理的話,會致使 Panel 組件沒必要要的渲染(雖然已經提供 Panel 層的 shouldComponentUpdate 的配置參數),同時也是爲了組件設計的高內聚、低耦合,咱們將傳入的 props 封裝到 NavBar 的 state 中,本身管理狀態。
constructor(props) { super(props); const navConfig = formatNavConfig(props.navConfig); this.state = { navConfig, }; } // 這裏咱們提供內部的 formatNavConfig 方法,具體內容根據不一樣組件業務需求不一樣代碼邏輯不一樣,這裏就不展開說明了
NavBar 中還須要注意的就是被動更新:Panel 層點擊後,NavBar 上文字的更新,由於這裏咱們利用父組件來進行 Panel 和 NavBar 的通訊
//Filter.js 調用 NavBar 的方法 /** * 更新 Navbar 文案 */ handleNavTextChange = (index, navText, isChange = true) => { // Navbar 的 render 抽離到內部處理,能夠減小一次 Filter.Panel 的額外 render this.asyncTask(() => { this.refNavbar.updateOptions(index, navText, isChange); }); }; //NavBar.js 提供給 Filter.js 調用的 updateOptions /** * 更新 navConfig,Filter 組件調用 * 異步 setState 規避 rax 框架 bug: 用戶在 componentDidMount 函數中調用中 this.props.onChange 回調 * 重現Code:https://jsplayground.taobao.org/raxplayground/cefec50a-dfe5-4e77-a29a-af2bbfcfcda3 * @param index * @param text * @param isChange */ updateOptions = (index, text, isChange = true) => { setTimeout(() => { const { navConfig } = this.state; this.setState({ navConfig: navConfig.map((item, i) => { if (index === i) { return { ...item, text, isChange, }; } return item; }), }); }, 0); };
最後 NavBar 中的 item 分爲 快速搜索和帶有 panel 的 NavBarItem兩種,可是對於其公共功能,好比渲染的 UI 邏輯等,這裏咱們採用的方法是抽離 NavBase
組件,供給 NavQuickSearch
和 NavRelatePanel
調用:
renderDefaultItem = ({ text, icons, active }) => { const { formatText, hasSeperator, length, keepHighlight, isChange } = this.props; const hasChange = keepHighlight && isChange; const iconWidth = icons ? this.getStyle('navIcon').width || 18 : 0; return [ <Text numberOfLines={1} style={[ this.getStyle('navText'), ifElse(active || hasChange, this.getStyle('activeNavText')), { maxWidth: 750 / length - iconWidth }, ]}> {ifElse(is('Function')(formatText), formatText(text), text)} </Text>, ifElse( icons, <Image ref={r => { this.refImg = r; }} style={this.getStyle('navIcon')} source={{ uri: ifElse(active || hasChange, icons && icons.active, icons && icons.normal), }} />, null, ), ifElse(hasSeperator, <View style={this.navSeperatorStyle} />), ]; };
export default class NavRelatePanel extends NavBase { static displayName = 'NavRelatePanel'; handleClick = () => { const { disabled, onNavbarPress } = this.props; if (disabled) return false; onNavbarPress(NAV_TYPE.RelatePanel); }; render() { const { renderItem, active, text, icons } = this.props; return ( <View style={[this.getStyle('navItem'), ifElse(active, this.getStyle('activeNavItem'))]} onClick={this.handleClick}> {ifElse( is('Function')(renderItem), renderItem && renderItem({ active, instance: this }), this.renderDefaultItem({ text, icons, active }), )} </View> ); } }
Panel 的核心功能是對用戶定義的 Panel.child 進行基本的功能添加,好比背景 mask 遮罩、動畫時機的處理.
Panel 的使用:
<Panel displayMode={'Fullscreen'} // 配置 Panel 全屏展現,默認爲下拉展現 animation={{ // 動畫配置 timingFunction: 'cubic-bezier(0.22, 0.61, 0.36, 1)', duration: 200, direction: 'left', // 動畫方向:從右往左方向滑出 }}> <MultiSelect {...this.state.data3} /> </Panel>
咱們提供基礎的動畫配置,可是同時,也提供動畫的 functionHook,這些都取決於動畫的觸發時機
get animationConfig() { const { animation } = this.props; if (!animation || !is('Object')(animation)) { return PANEL_ANIMATION_CONFIG; } return Object.assign({}, PANEL_ANIMATION_CONFIG, animation); } // ... /** * 執行動畫 * @param nextProps */ componentWillReceiveProps(nextProps) { if (nextProps.visible !== this.props.visible) { if (nextProps.visible) { setNativeProps(findDOMNode(this.refPanelContainer), { style: { transform: `translateX(-${rem2px(750)})`, }, }); this.props.disableNavbarClick(true); this.enterAnimate(this.currentChildref, () => { this.props.disableNavbarClick(false); }); this.handleMaskAnimate(true); } else { this.handleMaskAnimate(false); this.props.disableNavbarClick(true); this.leaveAnimate(this.currentChildref, () => { this.props.disableNavbarClick(false); setNativeProps(findDOMNode(this.refPanelContainer), { style: { transform: 'translateX(0)', }, }); }); } } }
因爲動畫的執行須要時間,因此這個時間段,咱們應該給 Filter 中的 NavBar 加鎖 ,鎖的概念也一樣提供給用戶,畢竟業務邏輯咱們是不會侵入的,在上一次的搜索沒有結果返回時候,應該給 NavBar 加鎖,禁止再次點擊(雖然用戶能夠再 onchange 回調函數中處理,可是做爲組件,一樣應該考慮而且提供這個能力),一樣對於動畫也是如此,在該動畫正在執行的時候,應該禁止 NavBar 的再次點擊。上面的動畫配置效果以下:
Panel 中還有核心的處理或許就是關於動畫時機的處理。好比在觸發動畫前,咱們須要設置動畫初始狀態,可是如若以下寫法,會出現 Panel 閃動的現象,畢竟咱們經過第二次的事件輪訓回來才執行初始化,因此這裏,若是用戶配置啓動動畫,那麼咱們須要在 Panel 的最外層添加一個可見的 flag:默認進來 opacity
設置爲 0,當動畫初始狀態設置完畢後,在將最外層容器的 opacity
設置爲 1,其實 Panel 仍是閃了一下,只是你看不到而已。
// 設置動畫初始樣式 setTimeout(() => { setNativeProps(node, { style: { transform: !visible ? 'translate(0, 0)' : v, }, }); }, 0); // 執行動畫 setTimeout(() => { transition( node, { transform: visible ? 'translate(0, 0)' : v, }, { timingFunction: timingFunction, duration: duration, delay: 0, }, cb, ); }, 50);
設置動畫初始化樣式中添加:
setNativeProps(findDOMNode(this.refPanelContainer), { style: { opacity: 1, }, });
Filter 的組件看似簡單,可是若是想寫一個市場上較爲通用和普遍的 Filter 組件,不只僅是組件的顆粒度、耦合度和性能須要考慮,更多的是其中仍是有太多的業務邏輯須要去思考。對於目前的第一版(還未修改爲正式開源版),已經基本涵蓋了目前咱們可以想到的業務場景,也已經有相關業務落地使用。
固然,對於若是是直接放到業務中使用而不做爲開源組件的話,咱們可已經 Panel下的 child 經過 renderPortal 下降層級,經過 EventBus 或者 redux、mobx 等管理數據狀態。那樣會讓整個代碼邏輯看起來清晰不少。可是爲了下降bundle 大小,咱們儘量的減小通用包的使用以及第三方插件的依賴。
關於文章中沒有說起的想法或者對於這些Filter業務需求(坑)你有更好的處理方法和想法都歡迎在評論區交流~
關注公衆號: 【全棧前端精選】 每日獲取好文推薦。
公衆號內回覆 【1】,加入全棧前端學習羣,一塊兒交流。