日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > vue >内容正文

vue

vue 筛选组件_记一个复杂组件(Filter)的从设计到开发

發(fā)布時間:2023/12/10 vue 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 vue 筛选组件_记一个复杂组件(Filter)的从设计到开发 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

此文前端框架使用 rax,全篇代碼暫未開源(待開源)
原文鏈接地址:Nealyang/PersonalBlog

前言

貌似在面試中,你如果設(shè)計一個 react/vue 組件,貌似已經(jīng)是司空見慣的問題了。本文不是理論片,更多的是自己的一步步思考和實踐。文中會有很多筆者的思考過程,歡迎評論區(qū)多多交流和討論。

從需求討論、技術(shù)方案探討到編碼、到最終的測試,經(jīng)歷過了很多次的腦暴,也遇到過非常多的坑,其中有可能跟業(yè)務(wù)有關(guān)、也有可能跟框架有關(guān),基于這些坑,又討論了很多解決方案和非常 hack(歪門邪道)的對策。但是隨著時間的推移,再回頭看看當(dāng)時的 hack 代碼,很多都不太記得為什么這么寫了,所以這里簡單記錄下,Filter 組件的開發(fā)過程。以便后面查詢,更希望能大家一起探討,以求得更優(yōu)質(zhì)的代碼架構(gòu)和實現(xiàn)思路。

由于代碼編寫使用基于底層 weex 的 rax 框架,所以有些坑,或許對于正在使用 react 或者 vue 的你并不會遇到,可以直接忽略

說說業(yè)務(wù)

Filter,已經(jīng)常見的不可再常見的組件了,顧名思義,就是個篩選過濾器。我們先看看現(xiàn)有 app 上的一些 filter 展現(xiàn) 形式。既然做組件,我們就需要它足夠的通用,足夠的易于擴展。

  • 阿里拍賣的 Filter

  • 飛豬的 Filter

在說 Filter 的業(yè)務(wù)特征之前,我們先約束下每一部分的命名,以便于你更好的閱讀此文:

上面分別是拍賣和飛豬的 filter 頁面,從這兩個頁面中,我們大概可以總結(jié)出關(guān)于 Filter 的一下幾點業(yè)務(wù)畫像:

  • 隨著頁面滾動,Filter 可能具有吸附能力,但是可能距離頂部存在一定的距離
  • Panel 面板多樣性(點擊navItem 展開的面板)
  • Panel 面板以及 navItem 都可能會有動畫
  • navBar 內(nèi)容可變
  • panel 面板展示形式不定
  • panel 面板內(nèi)容可能非常復(fù)雜,需要考慮性能優(yōu)化
  • navBar 上可能存在非 Filter 的內(nèi)容(關(guān)注按鈕)
  • 有的navBar 的 navItem 沒有對應(yīng)的 panel 面板
  • Filter 上存在影響搜索結(jié)果但是沒有影響的”快排“按鈕
  • filter 配置參數(shù)能夠指定
  • 通過 url 傳入相關(guān)篩選 id 能夠初始化面板選中

最終組件產(chǎn)出

由于 rax 1.0 ts+hooks 開源版本還在開發(fā)中,所以倉庫鏈接暫時就不放上了
  • rax-pui-filter-utils : Filter 的內(nèi)部工具庫,僅供 Filter 開發(fā)者提供的工具庫
  • rax-pui-filter-tools:配合使用 Filter 的一些工具集,比如 提高性能的 HOC 組件、占位符組件等(可用可不用,根據(jù)自己業(yè)務(wù)需求來),思考原由:并不是每一個 Filter 的使用者都需要這些功能,做成可插拔式,為了降低沒必要的 bundle 大小
  • pui-filter:Filter 核心功能開發(fā)庫

效果圖:

console 處可見拋出的查詢參數(shù)

設(shè)計與思考

前端組件架構(gòu)圖(初版)

組件架構(gòu)圖(終板)

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 // 導(dǎo)出文件 ├─ panel │ └─ index.js // panel 面板組件代碼 └─ style.js

組件功能 Feature

  • 篩選頭 UI 可動態(tài)配置擴展,支持點擊動畫,提供三種篩選項類型
    • `RelatePanel`:篩選項關(guān)聯(lián)Panel型,即篩選頭和 Panel 是一對一關(guān)系,點擊篩選頭展示 Panel
    • `QuickSearch`:篩選項快速搜索排序型,即篩選頭沒有對應(yīng) Panel,點擊篩選頭直接觸發(fā)搜索
    • `PureUI`:純 UI占位類型,即純 UI 放置,不涉及搜索,比如訂閱按鈕場景
  • 篩選面板顯示隱藏統(tǒng)一管理,支持下拉和左滑展示隱藏動畫,統(tǒng)一搜索回調(diào)函數(shù)
  • Filter 組件在和業(yè)務(wù)面板隔離,支持任意組件接入,業(yè)務(wù)組件里搜索變更通過 onChange(params)回調(diào)函數(shù)來觸發(fā)
  • 提供了三種業(yè)務(wù)通用的面板組件
    • `rax-pui-list-select`,列表選擇業(yè)務(wù)面板
    • `rax-pui-location-select`,省市區(qū)級聯(lián)選擇業(yè)務(wù)面板
    • `rax-pui-multi-selection-panel`,多選業(yè)務(wù)面板,查看組件使用文檔
這里指的是 Filter 的功能 Feature,跟上文提及的 Filter 組件功能可能并不能完全覆蓋,但是我們提供解決方案,組件的設(shè)計始終秉持著不侵入業(yè)務(wù)的原則,所有與業(yè)務(wù)相關(guān)均給予配置入口。

期望組件使用形式

import Filter from 'rax-pui-filter';render(<FilternavConfig={[]}onChange={()=>{}}><Filter.Panel><業(yè)務(wù)組件1 /></Filter.Panel><Filter.Panel><業(yè)務(wù)組件2 /></Filter.Panel></Filter>);

組件功能與業(yè)務(wù)需求邊界劃分

何為業(yè)務(wù)功能何為組件功能,這個需要具體的探討,其實也沒有嚴(yán)格意義上的區(qū)分。說白了,就是你買個手機,他都會送你充電器。但是。。。為什么很多手機也送手機殼(小米、華為、榮耀)但是 iPhone 卻不送呢?所以到底是不是標(biāo)配?

對于我們這個組件,簡而言之:我們能做到的,我們都做!但是其中我們還是梳理出某些功能還是數(shù)據(jù)業(yè)務(wù)功能:

  • navBar 上每一個 navItem 展示什么文案、樣式屬于業(yè)務(wù)功能
  • 整個 Filter 的數(shù)據(jù)處理,包括 url 上的查詢參數(shù)需要拋給對應(yīng) navItem要展示的文案也是業(yè)務(wù)功能
  • Filter 是否點擊滾動到頂部也是業(yè)務(wù)功能,畢竟很多搜索頁 Filter 本身置頂。而且,對于 rax 而言,不同容器滾動方式還不同(但是我們提供這樣的方法給你去調(diào)用)
  • panel 面板里面數(shù)據(jù)請求、邏輯處理都是你自己的業(yè)務(wù)邏輯。Filter 只提供基本的容器能力和接口

換言之,Filter 里面任何功能都可以說為業(yè)務(wù)功能。但是我們需要提供 80%業(yè)務(wù)都需要的功能封裝作為 Filter 的 Future。這就是我們的目的。

根據(jù)上面的業(yè)務(wù)功能和組件功能的區(qū)分,我們就知道在使用 Filter 的時候,你應(yīng)該給我傳遞什么配置,以及什么方法。

Filter API

參數(shù)說明類型默認值(是否必填)navConfig篩選頭配置, 點擊查看詳細配置項

效果圖

Array<Object>- (必填)offsetTopFilter組件展開面板狀態(tài)下距離頁面頂部的高度,有兩種狀態(tài):固定位置和跟隨頁面滾動吸附置頂

固定位置 狀態(tài)下距離頁面頂部的高度

跟隨頁面滾動吸附置頂: 狀態(tài)下距離頁面頂部的高度

效果圖

Number0styles配置樣式,Filter中所有樣式都可使用styles集合對象來配置覆蓋

styles 格式

Object{}getStickyRef獲取 Sticky 節(jié)點的 ref 實例,用于滾動吸附場景,內(nèi)部配合 pm-app-plus 容器組件點擊 Filter 時自動吸附置頂

示例圖

FunctionkeepHighlight篩選條件改變后是否需要在篩選頭保持高亮

效果圖

BooleanfalseclickMaskClosable開啟 mask 背景的點擊隱藏BooleantrueonChangeFilter 搜索變更回調(diào)函數(shù)

簽名:Function(params:Object,index:Number, urlQuery: Object) => void

參數(shù):

params: Object 搜索參數(shù)

index:Number 觸發(fā)搜索的 Panel 搜索

urlQuery:Object URL query 對象

FunctiononPanelVisibleChangePanel 顯示隱藏回調(diào)函數(shù)

簽名:Function({ visible:Boolean, triggerIndex:Number, triggerType:String }) => void

參數(shù):

visible:Boolean 顯示隱藏標(biāo)志量

triggerIndex:Number觸發(fā)的篩選項索引值

triggerType:String 觸發(fā)類型

triggerType詳解 包含三種觸發(fā)類型

Navbar:來自篩選頭的點擊觸發(fā)

Mask:來自背景層的點擊觸發(fā)

Panel:來自Panel 的 onChange 回調(diào)觸發(fā)Function

Filter prop navConfig 數(shù)組配置詳解

navConfig

篩選項類型 type

  • RelatePanel:篩選項關(guān)聯(lián)Panel型,即篩選頭和 Panel 是一對一關(guān)系,點擊篩選頭展示 Panel
  • QuickSearch:篩選項快速搜索排序型,即篩選頭沒有對應(yīng) Panel,點擊篩選頭直接觸發(fā)搜索
  • PureUI:純 UI占位類型,即純 UI 放置,不涉及搜索,比如訂閱按鈕場景

注意 如果 navConfig 內(nèi)置的UI參數(shù)不滿足您的需求,請使用renderItem自定義渲染函數(shù)來控制篩選頭 UI

參數(shù)說明類型默認值(是否必填)type篩選項類型

三種類型

RelatePanel: 篩選項關(guān)聯(lián)數(shù)據(jù)面板類型

QuickSearch: 篩選項快速搜索排序類型

PureUI: 純 UI占位類型String'RelatePanel'text

注意RelatePanel類型生效篩選頭顯示文案

文字溢出用...展示String- (必填)icons

注意RelatePanel類型生效篩選頭 icon:normal 正常態(tài) 和 active 激活態(tài) 圖標(biāo)

數(shù)據(jù)格式

Object類型 :

String類型 :

效果圖

Object or String-options

注意QuickSearch類型生效快速搜索排序類型的數(shù)據(jù)源

數(shù)據(jù)格式

Array(必填)optionsIndex

注意QuickSearch類型生效快速搜索排序類型默認選中的索引String0optionsKey

注意QuickSearch類型生效指定快速搜索排序?qū)?yīng)的搜索 key,用到 onChange 回調(diào)中String不提供默認使用當(dāng)前篩選項的索引formatText文案格式化函數(shù)

簽名:Function(text:String) => text

參數(shù):

text: String 篩選頭文案Function(text)=>textdisabled禁用篩選頭點擊BooleantruehasSeperator是否展示右側(cè)分隔符

效果圖

BooleanfalsehasPanel當(dāng)前篩選頭是否有對應(yīng)的 panelBooleantruerenderItem自定義渲染

注意

提供的配置項無法滿足你的 UI 需求時使用

簽名:Function(isActive:Boolean, this:Element) => Element

參數(shù):

isActive:Boolean 篩選頭是否為激活狀態(tài)

this:Element 篩選頭this實例Function-animation動畫配置,采用內(nèi)置的動畫

參數(shù)說明

注意 目前只內(nèi)置了一種rotate動畫類型ObjectanimationHook用戶自定義動畫的鉤子函數(shù),內(nèi)置動畫無法滿足需求時使用

簽名:Function(refImg:Element, isActive:Boolean) => text

參數(shù):

refImg:Element 篩選頭圖標(biāo)的 ref 實例

isActive:Boolean 篩選頭是否為激活狀態(tài)Function-

Filter.Panel API

參數(shù)說明類型默認值(是否必填)styles配置樣式

Filter中所有樣式都可使用styles集合對象來配置覆蓋Object{}displayModePanel 展現(xiàn)形式:全屏、下拉

參數(shù)說明

全屏:Fullscreen

下拉:DropdownString'Dropdown'noAnimation禁止動畫BooleantruehighPerformance內(nèi)部通過 Panel 的顯示隱藏控制 panel 的 render 次數(shù),避免不必要的 render,高性能模式下,只會在 Panel 展示 或者 展示隱藏狀態(tài)變化時才會重新 renderBooleantrueanimationPanel 展示動畫配置,內(nèi)置上下左右動畫

參數(shù)說明

direction 控制動畫方向,分別有 up、down、left、rightObject

Filter 的代碼使用

  • Filter 的參數(shù)配置
navConfig: [{type: 'RelatePanel', // type可以不提供,默認值為'RelatePanel'text: '向下', // 配置篩選頭文案icons: {// 配置 icon,分為正常形態(tài)和點擊選中形態(tài)normal: '//gw.alicdn.com/tfs/TB1a7BSeY9YBuNjy0FgXXcxcXXa-27-30.png',active: '//gw.alicdn.com/tfs/TB1NDpme9CWBuNjy0FhXXb6EVXa-27-30.png',},hasSeperator: true, // 展示豎線分隔符formatText: text => text + '↓', // 篩選文案的格式化函數(shù)},{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: '旋轉(zhuǎn)',icons: {// 配置 icon,分為正常形態(tài)和點擊選中形態(tài)normal: '//gw.alicdn.com/tfs/TB1PuVHXeL2gK0jSZFmXXc7iXXa-20-20.png',active: '//gw.alicdn.com/tfs/TB1l4lIXhv1gK0jSZFFXXb0sXXa-20-20.png',},animation: { type: 'rotate' }, // 配置動畫點擊后旋轉(zhuǎn)圖片,默認沒有動畫},{type: 'RelatePanel', // type可以不提供,默認值為'RelatePanel'text: '向左',},{type: 'PureUI',text: '訂閱',renderItem: () => {// 渲染自定義的 UIreturn (<Imagestyle={{width: 120,height: 92,}}source={{ uri: 'https://gw.alicdn.com/tfs/TB1eubQakL0gK0jSZFAXXcA9pXa-60-45.png' }}/>);},},]// ...<FilteroffsetTop={100} // offsetTop = RecycleView上面的組件的高度,當(dāng)前為 100navConfig={this.state.navConfig} // Filter Navbar 配置項keepHighlight={true} // 保持變更的高亮styles={styles} // 配置覆蓋內(nèi)置樣式,大樣式對象集合onChange={this.handleSearchChange}// Panel 面板顯示隱藏變更事件onPanelVisibleChange={this.handlePanelVisibleChange}><Panel highPerformance={true}><ListSelect {...this.state.data1} /></Panel><Panel><LocationSelect {...this.state.data2} /></Panel><PaneldisplayMode={'Fullscreen'} // 配置 Panel 全屏展示,默認為下拉展示animation={{// 動畫配置timingFunction: 'cubic-bezier(0.22, 0.61, 0.36, 1)',duration: 200,direction: 'left', // 動畫方向:從右往左方向滑出}}><MultiSelect {...this.state.data3} /></Panel></Filter>

代碼運行效果圖如上截圖。下面,簡單說下代碼的實現(xiàn)。

核心源碼展示

開源版本(Ts+hooks+lerna)還未公布,所以目前還是采用 rax 0.x 的版本編寫的代碼。這里只做,有坑的地方代碼處理講解。歡迎各位大佬評論留出各位想法

Filter.js

先從 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()}<Navbarref={r => {this.refNavbar = r;}}navConfig={navConfig}styles={styles}keepHighlight={keepHighlight}activeIndex={activeIndex}onNavbarPress={this.handleNavbarPress}onChange={this.handleSearchChange}/></View>);}

獲取一些基本配置,以及 windowHeight(屏幕高度)和 activeIndex(當(dāng)前第幾個item 處于 active 狀態(tài)(被點開))。

之所以我們的 renderPanels 寫在 NavBar 上面,是因為在 weex 中,zIndex 是不生效的。若想 A 元素在 B 元素上面,則 render 的時候,A 必須在 B 后面。這樣寫是為了 panel 面板展開的下拉動畫,看起來是從 navBar 下面出來的。

renderPanel 方法就是渲染對應(yīng)的 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;});};

準(zhǔn)確的說,這是一個 HOC,我們將代理、翻譯傳給 Filter 的影響或者 panel 面板需要使用的 props 傳遞給 Panel 面板。比如 onChange 回調(diào),或者面板隱藏的回調(diào)以及當(dāng)前哪一個 panel 需要展開等。

由于 Panel 的面板復(fù)雜度我們未知。為了避免不斷的展開和收齊不必要的 render,我們采用 transform的方式,將面板不需要顯示的面板移除屏幕外,需要展示的在移入到屏幕內(nèi)部。具體可見 Panel 的render return

return (<Viewref={r => {this.refPanelContainer = r;}}style={[defaultStyle.panel,styles.panel,this.panelContainerStyle,{transform: `translateX(-${this.containerTransformDes})`,opacity: 0,},]}><Viewref="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 里面需要請求數(shù)據(jù),然后頁面 url 里查詢參數(shù)有 locationId=123 ,navItem 需要展示對應(yīng)的地理位置.如果不渲染 Panel 如何根據(jù) id 拿到對應(yīng)的地名傳遞給 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。

當(dāng)然,Panel 也有很多別的坑,比如,現(xiàn)在 Panel 為了重復(fù) render,將 Panel 移除屏幕外,那么,動畫從上而下展開設(shè)置初始動畫閃屏如何處理?

Filter 的代碼就是初始化、format、檢查校驗各種傳參,以及 Panel 和 NavBar 通信中轉(zhuǎn) 比如 format、比如 handleNavbarPress

NavBar 核心代碼

NavBar 架構(gòu)

核心代碼

從架構(gòu)圖中大概可以看出,NavBar 中通過不同的配置,展示不同的 NavBarItem 的類型,NavQuickSearch,NavRelatePanel

這里需要注意的是: NavBar 的數(shù)據(jù)是通過 Filter props 傳入的,如果狀態(tài)放到 Filter 也就是 NavBar 的父組件管理的話,會導(dǎo)致 Panel 組件不必要的渲染(雖然已經(jīng)提供 Panel 層的 shouldComponentUpdate 的配置參數(shù)),同時也是為了組件設(shè)計的高內(nèi)聚、低耦合,我們將傳入的 props 封裝到 NavBar 的 state 中,自己管理狀態(tài)。

constructor(props) {super(props);const navConfig = formatNavConfig(props.navConfig);this.state = {navConfig,};}// 這里我們提供內(nèi)部的 formatNavConfig 方法,具體內(nèi)容根據(jù)不同組件業(yè)務(wù)需求不同代碼邏輯不同,這里就不展開說明了

NavBar 中還需要注意的就是被動更新:Panel 層點擊后,NavBar 上文字的更新,因為這里我們利用父組件來進行 Panel 和 NavBar 的通信

//Filter.js 調(diào)用 NavBar 的方法/*** 更新 Navbar 文案*/handleNavTextChange = (index, navText, isChange = true) => {// Navbar 的 render 抽離到內(nèi)部處理,可以減少一次 Filter.Panel 的額外 renderthis.asyncTask(() => {this.refNavbar.updateOptions(index, navText, isChange);});};//NavBar.js 提供給 Filter.js 調(diào)用的 updateOptions/*** 更新 navConfig,Filter 組件調(diào)用* 異步 setState 規(guī)避 rax 框架 bug: 用戶在 componentDidMount 函數(shù)中調(diào)用中 this.props.onChange 回調(diào)* 重現(xiàn)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 調(diào)用:

  • NavBase 部分代碼
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 [<TextnumberOfLines={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,<Imageref={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} />),];};
  • NavRelatePanel.js
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 (<Viewstyle={[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 的核心功能是對用戶定義的 Panel.child 進行基本的功能添加,比如背景 mask 遮罩、動畫時機的處理.

Panel 的使用:

<PaneldisplayMode={'Fullscreen'} // 配置 Panel 全屏展示,默認為下拉展示animation={{// 動畫配置timingFunction: 'cubic-bezier(0.22, 0.61, 0.36, 1)',duration: 200,direction: 'left', // 動畫方向:從右往左方向滑出}}><MultiSelect {...this.state.data3} /></Panel>

我們提供基礎(chǔ)的動畫配置,但是同時,也提供動畫的 functionHook,這些都取決于動畫的觸發(fā)時機

get animationConfig() {const { animation } = this.props;if (!animation || !is('Object')(animation)) {return PANEL_ANIMATION_CONFIG;}return Object.assign({}, PANEL_ANIMATION_CONFIG, animation);}// ... /*** 執(zhí)行動畫* @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)',},});});}}}

由于動畫的執(zhí)行需要時間,所以這個時間段,我們應(yīng)該給 Filter 中的 NavBar 加鎖 ,鎖的概念也同樣提供給用戶,畢竟業(yè)務(wù)邏輯我們是不會侵入的,在上一次的搜索沒有結(jié)果返回時候,應(yīng)該給 NavBar 加鎖,禁止再次點擊(雖然用戶可以再 onchange 回調(diào)函數(shù)中處理,但是作為組件,同樣應(yīng)該考慮并且提供這個能力),同樣對于動畫也是如此,在該動畫正在執(zhí)行的時候,應(yīng)該禁止 NavBar 的再次點擊。上面的動畫配置效果如下:

Panel 中還有核心的處理或許就是關(guān)于動畫時機的處理。比如在觸發(fā)動畫前,我們需要設(shè)置動畫初始狀態(tài),但是如若如下寫法,會出現(xiàn) Panel 閃動的現(xiàn)象,畢竟我們通過第二次的事件輪訓(xùn)回來才執(zhí)行初始化,所以這里,如果用戶配置啟動動畫,那么我們需要在 Panel 的最外層添加一個可見的 flag:默認進來 opacity 設(shè)置為 0,當(dāng)動畫初始狀態(tài)設(shè)置完畢后,在將最外層容器的 opacity 設(shè)置為 1,其實 Panel 還是閃了一下,只是你看不到而已。

// 設(shè)置動畫初始樣式setTimeout(() => {setNativeProps(node, {style: {transform: !visible ? 'translate(0, 0)' : v,},});}, 0);// 執(zhí)行動畫setTimeout(() => {transition(node,{transform: visible ? 'translate(0, 0)' : v,},{timingFunction: timingFunction,duration: duration,delay: 0,},cb,);}, 50);

設(shè)置動畫初始化樣式中添加:

setNativeProps(findDOMNode(this.refPanelContainer), {style: {opacity: 1,},});

結(jié)束語

Filter 的組件看似簡單,但是如果想寫一個市場上較為通用和廣泛的 Filter 組件,不僅僅是組件的顆粒度、耦合度和性能需要考慮,更多的是其中還是有太多的業(yè)務(wù)邏輯需要去思考。對于目前的初版(還未修改成正式開源版),已經(jīng)基本涵蓋了目前我們能夠想到的業(yè)務(wù)場景,也已經(jīng)有相關(guān)業(yè)務(wù)落地使用。

當(dāng)然,對于如果是直接放到業(yè)務(wù)中使用而不作為開源組件的話,我們可已經(jīng) Panel下的 child 通過 renderPortal 降低層級,通過 EventBus 或者 redux、mobx 等管理數(shù)據(jù)狀態(tài)。那樣會讓整個代碼邏輯看起來清晰很多。但是為了降低bundle 大小,我們盡可能的減少通用包的使用以及第三方插件的依賴。

關(guān)于文章中沒有提及的想法或者對于這些Filter業(yè)務(wù)需求(坑)你有更好的處理方法和想法都歡迎在評論區(qū)交流~

技術(shù)交流

歡迎關(guān)注微信公眾號:全棧前端精選,每日獲取高質(zhì)量文章推送。也可以加我個人微信交流~

總結(jié)

以上是生活随笔為你收集整理的vue 筛选组件_记一个复杂组件(Filter)的从设计到开发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。