用上帝視角來看待組件的設計模式

此文已由做者黃鍇受權網易雲社區發佈。
javascript

歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。html


組件設計,從簡單來看,就是如何提升編碼效率,提升代碼的複用率的方法,從高級來看,這是一門程序設計的藝術java

最近看了redux做者——Dan Abramov寫的《Presentational and Container Components》 感受受益不淺,發現有不少人都在討論組件模式,做爲一個每次寫需求就抓耳撓腮的思考如何組織代碼結構的人來講,如何更好的在工做中使用設計和使用組件,這確實是一個值得討論的問題。react

注:本文圖片部分參考於Michael Chan 作的有關React component patterns的演講: React component patterns, 有興趣的童鞋能夠去看看。git

前言

組合模式

首先,咱們先從宏觀的角度來聊聊組件。github

如今不少人如今都在談設計模式,設計模式。什麼是設計模式?設計模式主要是指GoF(四人組)《設計模式》一書中提出了23種設計模式,這23種設計模式被普遍應用在軟件工程中,表明着最佳的實踐方案。而在這些模式中,專門有一個設計模式叫:組合模式(Composite pattern),根據wikipedia的解釋:編程

組合模式的目的是將對象組合成樹型結構來表示部分-總體(part-whole)的層次結構。redux

組合模式使得用戶對單個對象和組合對象的使用具備一致性。設計模式

img

它有如下優勢安全

  • 可擴展性強:因爲組件間是充分解耦的,你能夠輕鬆的更新,替換組件

  • 編碼高效:因爲把功能拆解開,你能夠專一於每一個子模塊功能的實現。

javascript中的組合模式——組件

若是你在如今的工做中使用了框架,你會發現,這些框架基本都使用了組合模式這種設計模式。例如Vue和React的Components,它容許你編寫小的組件,而後經過他們的組合構建出你的實際應用。

æ ‘

在學校,總被老師說,要有大局觀,大局觀。使用組合模式,就能夠很好的鍛鍊你的大局觀。 這裏我以爲雲謙前輩 說的很貼切:

要記得,接到需求的第一步永遠不是寫代碼,而是想清楚你要作什麼,以上帝模式作總體設計。

開啓上帝視角

接下來,就讓咱們在咱們的編程世界扮演一個上帝(準確來講是女挖),看看咱們這個充滿組件世界到底發生了什麼。

組件的誕生

在框架類的語言中,組件就是咱們操做的基本單位,React對組件的操做提供了不少實用的API,一個組件就是經過使用這些API,產生了強大的生命力,你能夠把它看做一個功能完善的細胞或者個體,咱們全部的APP都是由這一個個細胞(個體)構成的。

img

而後,你在平常使用中會發現,有些組件,它老是使用其中的一部分API,有些組件常用另外一部分API。說明這些組件開始有了本身的思考,產生了差別化(你能夠把它看做基因突變)。當這些差別化愈來愈多,咱們發現,這些組件由於各自的能力偏好,逐減組成了不一樣的陣營:

image-20181019135735406

對於組件的陣營分類,有不少的說法:

  • 胖和瘦( Fat and Skinny,)組件

  • 狀態和無狀態/純(Stateful and Stateless/pure)組件

  • 聰明和愚笨(Smart and Dumb)組件

  • 容器和展現( Container and Presentational )組件……

他們本質上都差很少,這裏咱們選用Redux做者Dan Abramov對組件的稱法,叫他們容器展現組件,這裏使用Container和Components

組件分好了陣營,就要開始制定標準,正所謂無規矩不成方圓,一個良好的組織對肯定他們統一的旗號,核心價值觀,才能方便後續找到更多的同類人,發展壯大。

組件的陣營

容器組件(Container)陣營

他們稱本身是統治者,這裏匯聚的都是精英份子,它們習慣管理和組織。做爲高高在上的管理者,能讓下屬去作的事情本身必定不會動手。

所以他們制定了一套符合他們價值觀的標準:

  • 表明結構:我只關心

    事情如何運做的
    ,而不關心它是
    如何表現的

  • 無樣式:我這裏除了一些包裝div,基本沒有其餘標籤,而且從不具備任何樣式

  • 有狀態:我掌握着核心數據,剩下的事情大家去作

  • 一般由其餘組件生成:咱們一般表明更高等的智慧(通常使用更高階的組件生成)

  • 表明人物:各類Page頁面,路由頁面

展現組件(Components)陣營

他們一般由藍白領組成,表明這着廣大的工人階層,什麼髒活累活都是他們作,他們一般沒有太多想法,只是想簡單的作好本身的工做,少背鍋就好。

在這裏,每一個組件關心的事情不多,他們的志向只是作好本質工做,所以他們制定了一套符合他們工做習慣的標準:

  • 表明渲染:關注

    事物的外觀

  • 有樣式:基本上dom的渲染和樣式這些髒活累活都在這裏幹。

  • 強調獨立(很重要): 咱們彼此分工明確,不依賴於應用程序的其他部分(例如Flux操做或Store)。

  • 不關心數據:咱們不關心數據怎麼產生的,不要在咱們這裏指定數據的加載方式或變動方式。

  • 接受傳回指令:僅經過props接收數據和回調。

  • 弱狀態:咱們基本不須要有本身的狀態(當咱們這樣作時,它是UI狀態而不是數據)。

  • 表明人物:Search,SiderBar,UserList,Pagintation

這兩種陣營基本是徹底獨立的,因爲這種良好的劃分,他們在社會中能很好的相處(藍色是展現組件,灰色是容器組件。

img

實際運行

好了,看他們風風火火的把陣營分了,得給他們找點事幹,看這個社會分工的實際運行效果怎麼樣。

這裏順便提一句React的組件定義,一般有兩種方式類定義無狀態函數定義

無狀態函數定義:

這樣的好處是它自己是獨立的,沒有狀態的,它只會根據傳入的props(能夠考慮成指令)來修改本身的顯示,這樣的組件就具有很好的通用性,並且修改起來也方便,不用擔憂會影響到別的組件。

var User = ({name}) => (  <span>
    {name}  </span>);複製代碼

類定義:

若是有些內置的狀態須要維護,或者須要使用生命週期,可使用類定義的方式。

class User extends Component {
  constructor(props) {
    super(props);    this.state = {
      name: props.name,
      editable: false,
    };
  } 
  render(){     return ...
  }
}複製代碼

容器組件——定義結構,下發指令

容器組件能夠經過類定義,由於可能會使用到生命週期鉤子(使用dva也可能無需生命週期鉤子)。

若是使用了redux類的狀態管理機制,通常就會由connect生成(Relay的

createContainer()
,或Flux Utils的
Container.create()
)。

咱們如今要作一個用戶頁面,咱們把這個任務佈置下去,有一個容器陣營拿到了這個頁面製做指標,而後這些精英開始作組織架構工做,他們以爲完成這個工做須要造三個組件:UserSearchUserListUserModal。而後每一個組件須要的處理的指令(數據)也寫好了,放在userXXXProps裏,好了,他們的任務完成,開始去找展現容器陣營的人去實現這些功能。

function Users({ users }) {  // 從Redux裏獲取數據
  const {
    loading,
    list,
    total,
    current,
  } = users;  // 下發任務
  const userSearchProps = {};  const userListProps = {};  const userModalProps = {};  // 總體結構設計
  return (    <div>
      <UserSearch {...userSearchProps} />
      <UserList {...userListProps} />
      <UserModal {...userModalProps} />
    </div>
  );
}

// 指定訂閱數據,這裏關聯了 users
function mapStateToProps({ users }) {
  return { users };
}

// 創建數據關聯關係
export default connect(mapStateToProps)(Users);複製代碼

展現容器——接收指令,開工

通常來講,展現容器推薦使用 無狀態函數 的方式生成

一個良好的展現容器應該是彼此獨立的,這也是一個良好的分工社會應該具有的,你們各司其職,所以這裏一般使用無狀態函數生成一個組件。在這裏,一般是根據props傳入的值去處理相應的事情(老闆要你幹嗎就幹嗎)。

// 從props裏獲取指令(數據)const UserList = ({
  total,
  current,
  loading,
  dataSource,
}) => {const columns = [……];// 開始工做,構建組件的渲染return (  <div>
    <Table
      columns={columns}
      dataSource={dataSource}
      loading={loading}
    />
  </div>);
}複製代碼

這樣看下來,這種社會分工好像運行的挺順暢的。那這就是這個社會的全貌嗎?固然不是,實際的生活中,組件的區分能夠更加精確更加精細。

例若有一些組件設計模式裏把組件模式細分爲:

  • Proxy component

  • Presentational component

  • Layout component

  • Container component

  • Higher-order component (HOC's)

  • Render callback

可是,我我的認爲以上兩種劃分已經足夠應付生活中的絕大部分情形,剩下的,能夠經過二者的組合實現mixed Component

總結

這樣看下來,這種社會分工好像運行的挺順暢的,實際上,這種分類方式也是 不少開發者經驗的總結。

根據「約定優於配置(convention over configuration)」的思想,提早作必定的規範確定是沒錯的,但不少人將這種分工看作了他們設計組件的教條,這樣就很差了,由於凡事總有例外,也許在某些複雜的業務場景中,Container 和 Components須要混用。你須要靈活的去使用,這也是爲何Dan修改了本身文章:

I amended the article because people were taking the separation as a dogma. 90% of React users don’t ever plan to have something like a living styleguide. There is no need to make life harder for them. Even those that do, don’t need to make

every
component available in such tool.

適合本身的纔是最好的。

何苦讓生活更艱難?

最後,再說說比較好的應用開發步驟是什麼?我以爲多是:

  1. 完全弄清楚你要作什麼

  2. 功能的劃分

  3. 根據功能組織目錄結構

  4. 設計你的數據模型(modal)

  5. 設計你的容器,肯定總體框架結構

  6. 開始依次填充展現容器

  7. 鏈接組件和數據模型

  8. 測試

參考

Composite pattern

React component patterns

React Patterns

Presentational and Container Components

Container Components

React.js Conf 2015 - Making your app fast with high-performance components

Components and Props

Guide to Using the Composite Pattern with JavaScript

dva快速上手


免費體驗雲安全(易盾)內容安全、驗證碼等服務

更多網易技術、產品、運營經驗分享請點擊



相關文章:
【推薦】 [翻譯]pytest測試框架(一)

相關文章
相關標籤/搜索