聊聊組件設計

前言

組件化思想並非前端獨有的,但倒是前端技術的延伸 任何軟件開發過程,或多或少都有那麼一些組件化的需求css

隨着三大框架崛起,前端組件化逐漸成爲前端開發的迫切需求,一種主流,一種共識,它不只提升開發效率,同時也下降了維組件內聚原則護成本 開發者們不須要再面對一堆晦澀難懂的代碼,轉而只須要關注以組件⽅式存在的代碼⽚段 這是一場新的挑戰!前端

文章開始以前,明確本文的邊界

  • 從前端工程談到組件化開發
  • 組件的設計原則
  • 組件的職能劃分及利弊
  • 組件設計的邊界
  • 落實到具體業務中如何作
  • 一些感悟
  • 總結

一個面試題引起的思考

面試官一般會問 寫過前端通用組件嗎?
複製代碼

你可能會自信的表示: sure!vue

emm..是的嗎?react

從前端工程談到組件化開發

前端工程經歷的三個階段git

1. 庫/框架選型

image
肯定技術選型,爲項目節省許多工程量 後來三大框架的橫空出世,解放了很多生產力

2. 簡單構建優化

image

解決完開發效率,還須要兼顧運行性能, 故而選擇某種構建工具,對代碼進行壓縮,校驗,以後再以頁面爲單位進行簡單的資源合併github

3. JS/CSS模塊化開發

image

解決了基本開發效率和運行效率以後,開始考慮維護效率了web

分而治之(以分解下降複雜度)是軟件工程中的重要思想,是複雜系統開發和維護的基石,模塊化就是前端的分治手段面試

所以,模塊化強調的是拆分,最大的價值就是分治,意味着無論你未來是否要複用這塊兒代碼,都有將他們拆成一個模塊的理由編程

將一個大問題,不斷的拆解爲各個小問題進行分析研究,而後再組合到一塊兒(分而治之原則)element-ui

模塊化的方案

  • JS模塊化
無模塊化->函數寫法->對象寫法->自執行函數->CommonJS/AMD/CMD->ES6 Module
複製代碼
  • CSS模塊化
css模塊化是在less,sass等預處理器的支持下實現的
複製代碼

作到這些就夠了嗎?

固然是不夠的

模塊化強調的是拆分,不管是從業務角度仍是從架構、技術角度,模塊化首先意味着將代碼、數據等內容按照其職責不一樣分離

單純的橫向拆分業務功能模塊有一些問題

  • 面向過程的代碼 隨着業務的發展不利於維護
隨着業務發展,」過程線「也會愈來愈長,其餘項目成員根據各自須要,在」過程線「 加插各自邏輯,最終這個頁面的邏輯變得難以維護
咱們須要擺脫【一瀉而下】式的代碼編寫
複製代碼
  • 僅僅有JS/CSS模塊化是不夠的,UI(頁面)的分治也比較迫切
除了JS和CSS,界面也須要拆分,如何讓模塊化思想融入HTML語言

複製代碼

4. 組件化開發(本文重點)

組件化開發的演變

在大肆宣揚組件化開發概念以前,也經歷了尋求組件化最佳實踐的階段

頁面結構模塊化

image
簡單來講就是把頁面想象成樂高機器人,須要不一樣零件組裝,而後將各個部分拼到一塊兒

落實到實際開發中像這樣

咱們能夠獲取信息

  • 頁面pageModel包含了 tabContainerlistContainerimgsContainer 三個模塊
  • 咱們根據不一樣的業務邏輯封裝了不一樣類型的model
  • 每一個model有本身的數據,模板,邏輯,已經算是一個完整的功能單元

咦?嗅到一絲組件化的味道

N年前微軟的組件化的解決方案 HTML Component

歷史總有遺🐖

早在N年前微軟提出過一套解決方案,名爲HTML Component

jsworke

是一個比較完整的組件化方案了,但卻沒可以進入標準,默默地消失了,今天的角度來看,它能夠說是生不逢時

WebComponents 標準

當時」所謂的組件「

  • 此時的組件基本上只能達到某個功能單元上的集合,資源都是資源都是鬆散地分散在三種資源文件中
  • 並且組件做用域暴露在全局做用域下,缺少內聚性很容易就會跟其餘組件產生衝突(如最簡單的 css 命名衝突)

因而 W3C 按耐不住了,制定一個 WebComponents 標準,爲組件化的將來指引了明路

大體四部分功能

  • <template> 定義組件的 HTML模板能力
  • Shadow Dom 封裝組件的內部結構,而且保持其獨立性
  • Custom Element 對外提供組件的標籤,實現自定義標籤
  • import 解決組件結合和依賴加載

咱們思考一下,可行的實踐化方案須要具有哪些能力

  • 資源高內聚(組件資源內部高內聚,組件資源由自身加載控制)
  • 做用域獨立(內部結構密封,不與全局或其餘組件產生影響)
  • 自定義標籤(定義組件的使用方式)
  • 可相互組合(組件間組裝整合)
  • 接口規範化(組件接口有統一規範,或者是生命週期的管理)

三大框架出現

今天的前端生態裏面 React,Angular和Vue三分天下,即便它們定位不一樣,但核心的共同點就是提供了組件化的能力,算是目前是比較好的組件化實踐

1. Vue.js採用了JSON的方法描述一個組件
import PageContainer from './layout/PageContainer'
import PageFilter from './layout/PageFilter'

export default {
  install(Vue) {
    Vue.component('PageContainer', PageContainer)
    Vue.component('PageFilter', PageFilter)
  }
}

複製代碼

還提供了SFC(Single File Component,單文件組件)‘.vue’文件格式

<template>
//...
</template>

<script>
  export default {
    data(){}
  }
</script>

<style lang="scss">
//...
</style>
複製代碼
2. React.js發明了JSX,把CSS和HTML都塞進JS文件裏
class Tabs extends React.Component {
    render() {
        if (!this.props.items) {
            console.error('Tabs中須要傳入數據');
            return null;
        }
        const propId = this.props.id;
        return (
            <ul className={this.props.className}>
              <li>測試</li>
            </ul>
        );
    }
}
複製代碼
Angular.js選擇在本來的HTML上擴展
<input type="text" ng-model="firstname">

var app = angular.module('myApp', []);
app.controller('formCtrl', function($scope) {
    $scope.firstname = "John";
});
複製代碼

標準下的資源整合

image

具備如下特色

  • 每一個組件對應一個目錄,組件所需的各類資源都在這個目錄下就近維護;(最具軟件工程價值)
  • 頁面上的每一個獨立的可視/可交互區域視爲一個組件;
  • 因爲組件具備獨立性,能夠自由組合;
  • 頁面是組件的容器,負責組合組件造成功能完整的界面;
  • 當不須要某個組件,或者想要替換組件時,能夠整個目錄刪除/替換

應用結構圖

image

  • 分子是由原子組成的,分子分紅原子,原子也能夠從新組合成新的分子
  • 一個界面是由獨立的分子組件搭建而成,分子組件由原子元件構成,這些原子可經過不一樣的組合方式,組成新分子組件,繼而重組構成新的界面

模塊化與組件化對比

從總體概念來說

  • 模塊化是一種分治的思想,訴求是解耦,通常指的是js模塊,好比用來格式化時間的模塊
  • 組件化是模塊化思想的實現手段,訴求是複用,包含了templatestylescript,script又能夠由各類模塊組成

從複用的角度來說

  • 模塊通常是項目範圍內按照項目業務內容來劃分的,好比一個項目劃分爲子系統、模塊、子模塊,代碼分開就是模塊
  • 組件是按照一些小功能的通用性和可複用性抽象出來的,能夠跨項目,是可複用的模塊

從歷史發展角度來說

隨着前端開發愈來愈複雜、對效率要求越來高,由項目級模塊化開發,進一步提高到通用功能組件化開發,模塊化是組件化的前提,組件化是模塊化的演進

組件的設計原則

組件化方案下,咱們須要具備組件化設計思惟,它是一種【整理術】幫助咱們高效開發整合

  1. 標準性
任何一個組件都應該遵照一套標準,可使得不一樣區域的開發人員據此標準開發出一套標準統一的組件
複製代碼
  1. 獨立性
描述了組件的細粒度,遵循單一職責原則,保持組件的純粹性
屬性配置等API對外開放,組件內部狀態對外封閉,儘量的少與業務耦合
複製代碼
  1. 複用與易用
UI差別,消化在組件內部(注意並非寫一堆if/else)
輸入輸出友好,易用
複製代碼
  1. 追求短小精悍

  2. 適用SPOT法則

Single Point Of Truth,就是儘可能不要重複代碼,出自《The Art of Unix Programming》
複製代碼
  1. 避免暴露組件內部實現
  2. 避免直接操做DOM,避免使用ref
使用父組件的 state 控制子組件的狀態而不是直接經過 ref 操做子組件

複製代碼
  1. 入口處檢查參數的有效性,出口處檢查返回的正確性
  2. 無環依賴原則(ADP)

設計不當致使環形依賴示意圖

image

影響

組件間耦合度高,集成測試難 一處修改,到處影響,交付週期長 由於組件之間存在循環依賴,變成了「先有雞仍是先有蛋」的問題

那假若咱們真的遇到了這種問題,就要考慮如何處理

消除環形依賴

咱們的追求是沿着逆向的依賴關係便可尋找到全部受影響的組件

建立一個共同依賴的新組件

image

  1. 穩定抽象原則(SAP)
- 組件的抽象程度與其穩定程度成正比,
- 一個穩定的組件應該是抽象的(邏輯無關的)
- 一個不穩定的組件應該是具體的(邏輯相關的)
- 爲下降組件之間的耦合度,咱們要針對抽象組件編程,而不是針對業務實現編程
複製代碼
  1. 避免冗餘狀態
若是一個數據能夠由另外一個 state 變換獲得,那麼這個數據就不是一個 state,只須要寫一個變換的處理函數,在 Vue 中可使用計算屬性

若是一個數據是固定的,不會變化的常量,那麼這個數據就如同 HTML 固定的站點標題同樣,寫死或做爲全局配置屬性等,不屬於 state

若是兄弟組件擁有相同的 state,那麼這個state 應該放到更高的層級,使用 props 傳遞到兩個組件中

複製代碼
  1. 合理的依賴關係
父組件不依賴子組件,刪除某個子組件不會形成功能異常

複製代碼
  1. 扁平化參數
除了數據,避免複雜的對象,儘可能只接收原始類型的值
複製代碼
  1. 良好的接口設計
把組件內部能夠完成的工做作到極致,雖然提倡擁抱變化,但接口不是越多越好

若是常量變爲 props 能應對更多的場景,那麼就能夠做爲 props,原有的常量可做爲默認值。

若是須要爲了某一調用者編寫大量特定需求的代碼,那麼能夠考慮經過擴展等方式構建一個新的組件。

保證組件的屬性和事件足夠的給大多數的組件使用。

複製代碼
  1. API儘可能和已知概念保持一致

組件的職能劃分

那有了組件設計的「API」,就必定能開發出高質量的組件嗎?

組件最大的不穩定性來自於展示層,一個組件只作一件事,基於功能作好職責劃分

根據經驗,我將組件應分爲如下幾類

  • 基礎組件(一般在組件庫裏就解決了)
  • 容器型組件(Container)
  • 展現型組件(stateless)
  • 業務組件
  • 通用組件
    • UI組件
    • 邏輯組件
  • 高階組件(HOC)

基礎組件

爲了讓開發者更關注業務邏輯,涌現出了不少優秀的UI組件庫 好比antdelement-ui,咱們只須要調用API便能知足大部分的業務場景,前端角色後置了,開發變得更簡單了

容器型組件

一個容器性質的組件,通常看成一個業務子模塊的入口,好比一個路由指向的組件

image

特色

  • 容器組件內的子組件一般具備業務或數據依賴關係
  • 集中/統一的狀態管理,向其餘展現型/容器型組件提供數據(充當數據源)和行爲邏輯處理(接收回調)
  • 若是使用了全局狀態管理,那麼容器內部的業務組件能夠自行調用全局狀態處理業務
  • 業務模塊內子組件的通訊等統籌處理,充當子級組件通訊的狀態中轉站
  • 模版基本都是子級組件的集合,不多包含DOM標籤
  • 輔助代碼分離

表現形式🌰(vue)

<template>
<div class="purchase-box">
  <!-- 麪包屑導航 -->
  <bread-crumbs />
  <div class="scroll-content">
    <!-- 搜索區域 -->
    <Search v-show="toggleFilter" :form="form"/>
    <!--展開收起區域-->
    <Toggle :toggleFilter="toggleFilter"/>
    <!-- 列表區域-->
    <List :data="listData"/>
  </div>
</template>
複製代碼

展現型(stateless)組件

主要表現爲組件是怎樣渲染的,就像一個簡單的模版渲染過程

image

特色

  • 只經過props接受數據和回調函數,不充當數據源
  • 可能包含展現和容器組件 而且通常會有Dom標籤和css樣式
  • 一般用props.children(react) 或者slot(vue)來包含其餘組件
  • 對第三方沒有依賴(對於一個應用級的組件來講能夠有)
  • 能夠有狀態,在其生命週期內能夠操縱並改變其內部狀態,職責單一,將不屬於本身的行爲經過回調傳遞出去,讓父級去處理(搜索組件的搜索事件/表單的添加事件)

表現形式🌰(vue)

<template>
 <div class="purchase-box">
    <el-table
      :data="data"
      :class="{'is-empty': !data || data.length ==0 }"
      >
      <el-table-column
        v-for = "(item, index) in listItemConfig"
        :key="item + index" 
        :prop="item.prop" 
        :label="item.label" 
        :width="item.width ? item.width : ''"
        :min-width="item.minWidth ? item.minWidth : ''"
        :max-width="item.maxWidth ? item.maxWidth : ''">
      </el-table-column>
      <!-- 操做 -->
      <el-table-column label="操做" align="right" width="60">
        <template slot-scope="scope">
          <slot :data="scope.row" name="listOption"></slot>
        </template>
      </el-table-column>
      <!-- 列表爲空 -->
      <template slot="empty">
        <common-empty />
      </template>
    </el-table>
    
 </div>
  </template>
<script>
  export default {
    props: {
        listItemConfig:{ //列表項配置
        type:Array,
        default: () => {
            return [{
                prop:'sku_name',
                label:'商品名稱',
                minWidth:200
            },{
                prop:'sku_code',
                label:'SKU',
                minWidth:120
            },{
                prop:'product_barcode',
                label:'條形碼',
                minWidth:120
            }]
      }
    }}
  }
</script>
複製代碼

業務組件

一般是根據最小業務狀態抽象而出,有些業務組件也具備必定的複用性,但大多數是一次性組件

image

通用組件

能夠在一個或多個APP內通用的組件

UI組件

  • 界面擴展類組件,好比彈窗

image

特色:複用性強,只經過 props、events 和 slots 等組件接口與外部通訊

表現形式🌰(vue)

<template>
  <div class="empty">
    <img src="/images/empty.png" alt>
    <p>暫無數據</p>
  </div>
</template>
複製代碼

邏輯組件

  • 不包含UI層的某個功能的邏輯集合

高階組件(HOC)

高階組件能夠看作是函數式編程中的組合 能夠把高階組件看作是一個函數,他接收一個組件做爲參數,並返回一個功能加強的組件

高階組件能夠抽象組件公共功能的方法而不污染你自己的組件 好比 debouncethrottle

用一張圖來表示

jsworke

React中高階組件是比較經常使用的組件封裝形式,Vue官方內置了一個高階組件keep-alive經過維護一個cache實現數據持久化,但並未推薦使用HOC :(

在 React 中寫組件就是在寫函數,函數擁有的功能組件都有

Vue更像是高度封裝的函數,可以讓你輕鬆的完成一些事情,但與高度的封裝相對的就是損失必定的靈活,你須要按照必定規則才能使系統更好的運行
複製代碼

表現形式🌰(react)

品牌車系滑動的動畫

各種組件協同組成業務模塊

image

容器/展現組件

對比圖

image

引入容器組件的概念只是一種更好的組織方式

  • 容器組件專門負責和store通訊,把數據經過props傳遞給展現組件,展現組件若是數據須要更新,須要傳遞迴調給容器組件,在容器組件中執行具體操做(業務邏輯)來獲取更新結果
  • 展現型組件再也不直接和store耦合,而是經過props接口來定義所需的數據和方法,複用性與正確性更能保證
  • 展現型組件直接和store通訊的話,那麼一個展現型組件就會收到限制,由於你在store裏面的字段已經限制他的使用次數和使用的位置
  • 各司其職,不易出錯,即便出錯,也能快速定位問題

既然如此,那我何時引入容器組件,何時引入展現組件

引入容器組件的時機

優先考慮展現組件,當你意識到有一些中間組件不使用它繼承的props而是轉而傳遞給他們的子級,每次子級組件須要更多數據時,你都須要從新調整這些中間組件,那麼,這時候就要考慮引入容器組件

容器組件和展現組件的區別並無被嚴格定義,它們的區別不在技術上而是目的性上

這裏有幾個供參考的點

  • 容器組件傾向於有狀態,展現組件傾向於無狀態,這不是硬性規定,它們都是能夠有狀態的
  • 不要把分離容器組件和展現組件當作教條,若是你不肯定該組件是容器組件仍是展現組件,就暫時不要分離,寫成展現組件,也許是爲時尚早。彆着急!
  • 這是一個持續的重構過程,不用試圖一次就把它作好,習慣這種模式就會培養起一種直覺,知道什麼時候引入容器 就像你知道什麼時候封裝一個函數那樣兒

進行組件職能劃分的利弊

優勢

  • 更好的關注分離
用這種方式寫組件,你能夠更好的理解你的app和你的ui,甚至會逐漸造成你本身的開發套路
複製代碼
  • 複用性高
一個組件只作一件事,解除了組件的耦合帶來更高複用性
複製代碼
  • 它是app的調色版,設計師能夠隨意調整它的ui而不用改變app的邏輯
  • 這會強制你提取「佈局組件」,達到更高的易用性
  • 提升健壯性
因爲展現組件和容器組件是經過prop接口來鏈接,能夠利用props的校驗機制來加強代碼的可靠性,混合的組件就沒有這種好處

舉個🌰(Vue)
  props: {
    editData: Object,
    statusConfig: {
      type: Object,
      default() {
        return {
          isShowOption: true, //是否有操做欄
          isShowSaveBtn: false
        };
      }
    }
  }
複製代碼
  • 可測試性
組件作的事情更少了,測試也會變得容易
容器組件不用關心UI的展現,只關心數據和更新
展現組件只是呈現傳入的props,寫單元測試的時候也很是容易mock數據層
複製代碼

所謂的缺點

  • 由於容器組件/展現組件的拆分,初期會增長一些學習成本
  • 因爲須要封裝一個容器,包裝一些數據和接口給展現組件,會增長一些工做量
  • 在展現組件內對props的聲明會帶來少許的工做

長遠來看,利大於弊

組件設計的邊界

物極必反,躍躍欲試前,思考如下幾個問題以引導完善組件的設計

頁面層級不宜嵌套超過三層,切勿過分設計

超過三層以後可見組件的數據傳遞的過程就會變得越複雜
複製代碼

這個組件能否(有必要)再分?

  • 劃分粒度的根據實際狀況權衡,過小會提高維護成本,太大又不夠靈活和高複用性
  • 每個組件都應該有其獨特的劃分目的的,有的是爲了複用實現,有的是爲了封裝複雜度清晰業務實現
  • 組件劃分的依據一般是業務邏輯、功能,要考慮各組件之間的關係是否明確,及可複用度
  • 若是它只是幾行代碼,那麼最終可能會建立更多的代碼來分離它,有必要嗎?我這麼作的好處是否超過了成本?
  • 若是你當前的邏輯不太可能出如今其餘地方,那麼將它嵌入其中更好,若是須要,你能夠隨時抽離,畢竟組件化沒有終點
  • 性能會受到影響嗎? 若是狀態頻繁更改,而且當前在一個較大的,關係比較緊密的組件裏,爲了不性能受到影響最好抽離出來
  • 是否打破了一個邏輯上有意義的實體,假若抽離的話,這個代碼被複用的機率有多大

這個組件的依賴是否可再縮減?

縮減組件依賴能夠提升組件的可複用度

這個組件是否對其它組件形成侵入?

  • 封裝性不足或自身越界操做,就可能對自身以外形成了侵入
  • 一個組件不該對其它兄弟組件形成直接影響
較常見的一種狀況是:組件運行時對window對象添加resize監聽事件以實現組件響應視窗尺寸變化事件,這種需求的更好替代方案是:組件提供刷新方法,由父組件實現調用

次優的方案是,當組件destroy前清理恢復
複製代碼

這個組件能否複用於其它相似場景中?

須要考慮須要適用的不一樣場景,在組件接口設計時進行必要的兼容

這個組件當別人用時,會怎麼想?

接口設計符合規範和大衆習慣,儘可能讓別人用起來簡單易上手,易上手是指更符合直覺。

假如業務須要不須要這個功能,是否方便清除?

各組件以前以組合的關係互相配合,也是對功能需求的模塊化抽象,當需求變化時能夠將實現以模塊粒度進行調整

上文提到的各類準則僅僅描述了一種開發理念,也能夠認爲是一種開發規範,假若你承認這規範,對它的分治策略產生了共鳴,那咱們就能夠繼續聊聊它的具體實現了

問本身一個問題

你心中的相對完美的組件是什麼樣子的?

落實到具體業務中如何作

劃分依據

明確你的組件劃分依據,目前是兩種

  • 根據業務劃分
  • 根據技術劃分
  1. 我更多的是根據業務去設計我應用中的組件樹,可能會畫個草圖或xmind,它能夠幫我統觀全局
  2. 明確各個組件的邊界,內部state的設計,props的設計以及與其餘組件的關係(須要回調出去的事件)
  3. 明確各個組件的定位與職能劃分,設計好父子組件、兄弟組件的通訊機制
  4. 搭架子
  5. 架子有了,開始填空

切割模版(頁面結構模塊化)

這是最容易想到的方法,當一個組件渲染了不少元素,就須要嘗試分離這些組件的渲染邏輯 咱們以掘金頁面爲例

jsworke

大致上看,能夠分爲Part1,Part2,Part3

初步開發

<template>
  <div id="app">
    <div class="panel">
      <div class="part1 left">
        <!--內容-->
      </div>
      <div class="part1 right">
        <!--內容-->
      </div>
      <div class="part1 right">
        <!--內容-->
      </div>
  </div>
</template>

複製代碼

問題:

  • 代碼量大,難以維護,難以測試
  • 有些許重複量

化繁爲簡

<template>
  <div id="app">
      <part1 />
      <part2 />
      <part3 /> 
  </div>
</template>

複製代碼

好處:

  • 同以前的方式相比,這個微妙的改進是革命性的
  • 解決了測試困難,維護困難的問題

問題:

  • 沒有解決代碼重複的問題,這種按模塊劃分,複用性低

但我看過不少項目的代碼,就是這麼幹的,認爲本身作了組件化,抽象的還不錯(@_@)

組件抽象

它們有類似的外層,part2和part3更有類似的titlebar,除了業務內容,徹底就是如出一轍

🌰(vue)

<template>
  <div class="part">
    <header>
      <span>{{ title }}</span>
    </header>
    <slot name="content" />
  </div>
</template>


複製代碼

咱們將part內能夠抽象的數據都作成了props,利用slot去作模版 那麼咱們在開發相應Part1,Part2時

🌰(vue)

<template>
  <div id="app">
      <part title="亦舒">
        <div slot="content">----</div>
      </part>
      <part title="興隆臻園戶型">
        <div slot="content">-----</div>
      </part>
  </div>
</template>
複製代碼

更具表明性的示例圖

jsworke

  • UI差別在哪裏定義?

在業務邏輯層處理

首先要明確一點,這些差別並非組件自己形成的,是你本身的業務邏輯形成的,因此容器組件(父組件)應該爲此買單
複製代碼
  • 數據差別在哪裏定義?

結合組件自己和業務上下文將差別合理的消除在內部

好比part3中,其餘的part只有一個相似更多>>的link,可是它卻有多個(一居,二居...)
這裏我推薦將這種差別體如今組件內部,設計方法也不少:
好比能夠將link數組化爲links;
好比能夠將更多>>看做是一個default的link,而多餘的部分則是用戶自定義的特殊link,這二者合併組成了links。用戶自定義的默認是沒有的,須要引用組件時進行傳入。


複製代碼
  • 組件命名規則?

組件設計初期,就應該擁有不耦合業務的名字

一個通用的或者說將來可能通用的,要有相對合理的命名,好比 Search,List,儘可能不要出現與業務耦合過深的業務名詞,通用組件與業務無關,只與自身抽象的組件有關
咱們在設計組件初期,就應該有這種思想,等到真正能夠抽出公用組件了,再去苦逼的名更名字?
庫一般都想讓廣大開發者用,咱們在設計組件時,能夠下降標準到先作到你的整個APP中通用
複製代碼

組件劃分細粒度的考量(抽之有度)

組件設計規則明明白白寫着咱們要遵循單一職責原則,這也帶來了上文聊過的過分抽象(組件化)的問題,咱們結合具體的業務聊一下

jsworke

要實現徽章組件,它有兩部分組成

  • 按鈕
  • 右上角提示(小紅點/icon)

二者都是符合單一職責的,能夠將其抽離成一個獨立組件,可是一般不要這麼作

由於同一個app的風格必將是統一的,除此以外沒別的應用場景了,就像上文所說的,抽離組件以前,多問本身爲何以及投入/產出比,沒有絕對的規則
複製代碼

tips

單一職責組件要創建在可複用的基礎上,對於不可複用的單⼀職責組件咱們僅僅做爲獨立組件的內部組件便可

某二手車網站體現其細粒度的例子

jsworke

思考,若是讓你實現你會如何設計... 我當初是這麼設計的

jsworke

index.js(react)

<div className="select-brand-box" onTouchStart={touchStartHandler} onTouchMove={touchMoveHandler} onTouchEnd={touchEndHandler.bind(this, touchEndCallback)}>
     <NavBar></NavBar>
     <Brand key="brands-list" {...brandsProps} />
     <Series key="series-list" {...seriesProps} >
 </div>
 
 export default BrandHoc(index);

複製代碼

Brand.js(react)

<div className="brand-box">
    <div className="brand-wrap" ref="brandWrap">
        <p className="brands-title hot-brands-title">熱門品牌</p>
        <FlexLayout onClick={hotBrandClick}>
            <HotBrands HotBrands={hotBrands} />
        </FlexLayout>
        {!isHideStar && <UnlimitType {...unlimitProps} />}
        <AllBrands {...brandsProps} />
    </div>
    <AsideLetter {...asideProps} />
    {showPop ? <PopTips key="pop-tips" tip={currentLetter} /> : null}
    {showBrandLoading ? <Loading /> : null}
</div>
            

複製代碼

FlexLayout.js(react)

jsworke

這個示例幾乎涵蓋了全部的規則

  • 首先組件的設計是根據業務劃分的,因此右側字母導航(AsideLetter)纔沒有在最外層的容器組件,不然通訊問題會佔用一部分篇幅,事實上這是有解的
  • 入口組件是容器組件,事實上把它當作一個規則就好了,業務邏輯的載體
  • 除了容器組件外,其餘的組件都被抽成公用的了,二手車平臺相似的場景很是多

jsworke

  • 賣車平臺相似的圖文混排多且形態各不相同,應用場景普遍,抽!UI差別消化在組件內部,參考FlexLayout.js,給定default props
  • 可提取的組件過多(業務驅動)致使通信困難如何解決? 那說明你須要新增可管理狀態的容器組件,上例中Brand,Series也是容器組件,負責管理子組件的大小事宜
  • 細粒度的考量,考慮付出產出比
<p className="brands-title hot-brands-title">熱門品牌</p> 只有一行,直接寫就完了

複製代碼
  • 組件抽離的過程就是無限向無狀態(展現型)組件無限靠近的過程

通用性考量

組件的形態(UI)永遠是變幻無窮的,可是其行爲(邏輯)是固定的,所以通用組件的祕訣之⼀就是將DOM 結構的控制權交給開發者,組件只負責⾏爲和最基本的DOM結構

這是一個顯眼的栗子

某一天,你接到這樣兒的需求

jsworke

開心,簡單,三下五除二寫完了

忽然有一天又有這樣兒的需求

jsworke

emm..可定製?以前的select無法用了,怎麼作?要修改上一個或者再寫一個嗎? 一旦出現了這種狀況,證實以前的組件須要從新設計了

實現通用性設計的關鍵一點是放棄對Dom的掌控

那麼問題又來了,那麼多須要自定義的地方,那組件會不會很難用?

通用性設計在將Dom結構決定權交給開發者的同時指定默認值

這裏是一個新鮮出爐(vue)🌰

List組件

jsworke

父組件🌰(vue)及slot

模版(僞代碼)
<template>
<List :data="tableData[item.type]" :loading="loading" @loadMore="loadMore" :noMore="noMore">
    <a v-if="item.type == 0" slot="listOption" slot-scope="childScope" class="edit-btn" @click="edit(childScope.data)" v-bind:key="childScope.data.id">{{Status[childScope.data.status]['text']}}</a>
</List>
</template>

config(僞代碼)
export const Status = {
  //....
  1: {
    label: '草稿',
    type: '',
    text: '編輯',
    class: 'note'
  }}
  //...
複製代碼

又有一個栗子(vue)

jsworke

  • Dialog只負責基礎的邏輯,交出控制權給到業務,至於你的業務須要什麼,在容器組件(業務邏輯層)去處理

忍不住放上磐石業務的反面例子

jsworke

難用無非是兩方面的問題

  1. 不願移交控制權
  2. 沒有API文檔

全部的業務邏輯與場景都包含在組件內部,外界只經過變量來控制,初衷是好的,可是隨着業務發展,組件愈來愈龐大,開發者也愈來愈力不從心了

恰好現階段UI改版,咱們的工做量就由只改樣式直接轉化爲推倒重來了,又沒有詳細的文檔,工做量瞬間翻了N倍😭寶寶內心苦寶寶不說

善用設計模式

其實一開始,我並無專門去套用設計模式,徹底是業務驅使 你必定見到過這樣兒的

jsworke

一旦這樣兒的邏輯多了,那是否是就跟業務耦合了,跟業務耦合多了,那組件天然沒有什麼通用性了,即便咱們不考慮到通用性,那寫的累吧?

考慮下這樣寫會不會好一點

config(僞代碼)
export const Status = {
  4: {
    label: '部分入庫',
    type: '',
    text: '查看'
  }
}
模版(vue)
<a v-if="item.type == 0" slot="listOption" slot-scope="childScope" class="edit-btn" @click="edit(childScope.data)" v-bind:key="childScope.data.id">{{Status[childScope.data.status]['text']}}</a>

複製代碼

世界上本沒有設計模式,寫的人多了,就自成一套脫穎而出進而被歷史銘記了!不只如此,一部分看似複雜的業務若是合理設計配置項,能夠會爲你省去一大篇js

一些感悟

像磐石這種底層的業務支持系統,離不開大量的列表,查詢,編輯,詳情等,我通常會花30秒搭好架子,像但不限於下面這種

jsworke

  • index:模塊入口(承擔容器職責)
  • api:整塊業務的API
  • components 業務組件集合
1. Form:表單 通常會被add.vue(編輯) 和edit.vue(詳情)引用
2. List:列表
3. Search: 搜索組件
4. 其餘業務中有但卻沒看到的基本上都已經抽離到common了 好比麪包屑導航,收起展開功能等
複製代碼
  • libs 頁面的各類配置

具體體現(磐石剛剛重構的模塊)

採購模塊結構圖

image

form

image

edit

image

不管有多少種狀態,只在edit這層容器維護

要這麼作的緣由

  • components中的組件只是暫存,都有可能被升級成通用組件,因此命名要注意,一類的保持了統一,防止業務耦合
  • bug有跡可循,數據的問題我必定從外向裏排查,樣式問題從裏向外排查,定位問題快
  • 與重複代碼作鬥爭,時刻保持一種強迫症的心態去整理各個模塊,造成本身的編碼風格,進而團隊風格纔有可能統一

總結

  • 對於組件設計,充分的準備當然,但在現實世界中,切實的結果纔是最重要的,組件設計也不要過分設計更不要停滯不前,該作的時候就去作,發現很差就去改
  • 有空閒時間就去思考早期不夠理想的代碼,它能夠做爲咱們向前發展的基礎
  • 技術在變遷,但組件化的核心並無改變,目標仍然是在API設計儘量接近原生的狀況下完成複用、解耦、封裝、抽象的目標,最終服務於開發,提升效率下降錯誤率
  • 組件化是對實現的分層,是更有效地代碼組合方式
  • 組件化是對資源的重組和優化,從而使項目資源管理更合理,方便拔插、方便集成、方便刪除、方便刪除後從新加入
  • 這種化繁爲簡的思想在後端開發中的體現是微服務,而在前端開發中的體現就是組件化
  • 組件化有利於單元測試與自測效率對重構較友好
  • 新人加入能夠直接分配組件進行開發、測試,而非須要熟悉整個項目,能夠從一個組件的開發使新進人員比較快速熟悉項目、瞭解到開發規範
  • 你的直接責任多是編寫代碼,但你的終極目標是在建立產品

最後說一句

組件化沒有終點,day day up

參考連接

相關文章
相關標籤/搜索