主頁 > 軟體設計 > 記一個復雜組件(Filter)的從設計到開發

記一個復雜組件(Filter)的從設計到開發

2020-09-15 16:47:21 軟體設計

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

前言

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

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

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

說說業務

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

  • 阿里拍賣的 Filter

paimai

  • 飛豬的 Filter

feizhu

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

IMAGE

上面分別是拍賣和飛豬的 filter 頁面,從這兩個頁面中,我們大概可以總結出關于 Filter 的一下幾點業務畫像:

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

最終組件產出

由于 rax 1.0 ts+hooks 開源版本還在開發中,所以倉庫鏈接暫時就不放上了

  • rax-pui-filter-utils : Filter 的內部工具庫,僅供 Filter 開發者提供的工具庫
  • rax-pui-filter-tools:配合使用 Filter 的一些工具集,比如 提高性能的 HOC 組件、占位符組件等(可用可不用,根據自己業務需求來),思考原由:并不是每一個 Filter 的使用者都需要這些功能,做成可插拔式,為了降低沒必要的 bundle 大小
  • pui-filter:Filter 核心功能開發庫

效果圖:

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

組件功能 Feature

  • 篩選頭 UI 可動態配置擴展,支持點擊影片,提供三種篩選項型別
    • RelatePanel篩選項關聯Panel型,即篩選頭和 Panel 是一對一關系,點擊篩選頭展示 Panel
    • QuickSearch篩選項快速搜索排序型,即篩選頭沒有對應 Panel,點擊篩選頭直接觸發搜索
    • PureUI純 UI占位型別,即純 UI 放置,不涉及搜索,比如訂閱按鈕場景
  • 篩選面板顯示隱藏統一管理,支持下拉和左滑展示隱藏影片,統一搜索回呼函式
  • Filter 組件在和業務面板隔離,支持任意組件接入,業務組件里搜索變更通過 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 卻不送呢?所以到底是不是標配?

對于我們這個組件,簡而言之:我們能做到的,我們都做!但是其中我們還是梳理出某些功能還是資料業務功能:

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

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

根據上面的業務功能和組件功能的區分,我們就知道在使用 Filter 的時候,你應該給我傳遞什么配置,以及什么方法,

Filter API

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

效果圖
undefined
Array<Object> - (必填)
offsetTop Filter組件展開面板狀態下距離頁面頂部的高度,有兩種狀態:固定位置跟隨頁面滾動吸附置頂

固定位置 狀態下距離頁面頂部的高度
跟隨頁面滾動吸附置頂: 狀態下距離頁面頂部的高度

效果圖
undefined
Number 0
styles 配置樣式,Filter中所有樣式都可使用styles集合物件來配置覆寫
styles 格式
undefined
Object
getStickyRef 獲取 Sticky 節點的 ref 實體,用于滾動吸附場景,內部配合 pm-app-plus 容器組件點擊 Filter 時自動吸附置頂

示例圖
undefined
Function
keepHighlight 篩選條件改變后是否需要在篩選頭保持高亮

效果圖
undefined
Boolean false
clickMaskClosable 開啟 mask 背景的點擊隱藏 Boolean true
onChange Filter 搜索變更回呼函式
簽名: Function(params:Object,index:Number, urlQuery: Object) => void
引數:
params: Object 搜索引數
index:Number 觸發搜索的 Panel 搜索
urlQuery:Object URL query 物件
Function
onPanelVisibleChange Panel 顯示隱藏回呼函式
簽名: Function({ visible:Boolean, triggerIndex:Number, triggerType:String }) => void
引數:
visible:Boolean 顯示隱藏標志量
triggerIndex:Number觸發的篩選項索引值
triggerType:String 觸發型別


triggerType詳解 包含三種觸發型別
Navbar:來自篩選頭的點擊觸發
Mask:來自背景層的點擊觸發
Panel:來自Panel 的 onChange 回呼觸發
Function

Filter prop navConfig 陣列配置詳解

篩選項型別 type

  • RelatePanel篩選項關聯Panel型,即篩選頭和 Panel 是一對一關系,點擊篩選頭展示 Panel
  • QuickSearch篩選項快速搜索排序型,即篩選頭沒有對應 Panel,點擊篩選頭直接觸發搜索
  • PureUI純 UI占位型別,即純 UI 放置,不涉及搜索,比如訂閱按鈕場景

注意 如果 navConfig 內置的UI引數不滿足您的需求,請使用renderItem自定義渲染函式來控制篩選頭 UI

引數 說明 型別 默認值(是否必填)
type 篩選項型別

三種型別
RelatePanel: 篩選項關聯資料面板型別
QuickSearch: 篩選項快速搜索排序型別
PureUI: 純 UI占位型別
String 'RelatePanel'
text


注意 RelatePanel型別生效
篩選頭顯示文案
文字溢位用...展示
String - (必填)
icons


注意 RelatePanel型別生效
篩選頭 icon:normal 正常態 和 active 激活態 圖示
資料格式
Object型別 :
undefined
String型別 :
undefined

效果圖
undefined
Object or String -
options


注意 QuickSearch型別生效
快速搜索排序型別的資料源
資料格式
undefined
Array (必填)
optionsIndex


注意 QuickSearch型別生效
快速搜索排序型別默認選中的索引 String 0
optionsKey


注意 QuickSearch型別生效
指定快速搜索排序對應的搜索 key,用到 onChange 回呼中 String 不提供默認使用當前篩選項的索引
formatText 文案格式化函式
簽名:Function(text:String) => text
引數:
text: String 篩選頭文案
Function (text)=>text
disabled 禁用篩選頭點擊 Boolean true
hasSeperator 是否展示右側分隔符

效果圖
undefined
Boolean false
hasPanel 當前篩選頭是否有對應的 panel Boolean true
renderItem 自定義渲染
注意
提供的配置項無法滿足你的 UI 需求時使用
簽名:Function(isActive:Boolean, this:Element) => Element
引數:
isActive:Boolean 篩選頭是否為激活狀態
this:Element 篩選頭this實體
Function -
animation 影片配置,采用內置的影片
引數說明
undefined
注意 目前只內置了一種rotate影片型別
Object
animationHook 用戶自定義影片的鉤子函式,內置影片無法滿足需求時使用
簽名:Function(refImg:Element, isActive:Boolean) => text
引數:
refImg:Element 篩選頭圖示的 ref 實體
isActive:Boolean 篩選頭是否為激活狀態
Function -

Filter.Panel API

引數 說明 型別 默認值(是否必填)
styles 配置樣式
Filter中所有樣式都可使用styles集合物件來配置覆寫
Object
displayMode Panel 展現形式:全屏、下拉
引數說明
全屏:Fullscreen
下拉:Dropdown
String 'Dropdown'
noAnimation 禁止影片 Boolean true
highPerformance 內部通過 Panel 的顯示隱藏控制 panel 的 render 次數,避免不必要的 render,高性能模式下,只會在 Panel 展示 或者 展示隱藏狀態變化時才會重新 render Boolean true
animation Panel 展示影片配置,內置上下左右影片
引數說明
undefined
direction 控制影片方向,分別有 updownleftright
Object

Filter 的代碼使用

  • Filter 的引數配置
  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 的版本撰寫的代碼,這里只做,有坑的地方代碼處理講解,歡迎各位大佬評論留出各位想法

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()}
        <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 組件,供給 NavQuickSearchNavRelatePanel 呼叫:

  • 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 [
      <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} />),
    ];
  };
  • 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 (
        <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 的核心功能是對用戶定義的 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】,加入全堆疊前端學習群,一起交流,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/48395.html

標籤:架構設計

上一篇:【Web API】通俗講解 RESTful

下一篇:一次跨行取款失敗,而引發對分布式事物的思考

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more