「 思考 」 React Hooks 的設計哲學

React Hooks Complete Guide | useState() | useEffect() | Rules for React  Hooks

背景

React Hooks 已經出來有段時間了, 不少小夥伴或多或少都用過。javascript

今天呢,咱們就回頭再看一下這個東西,思考一下,這個東西爲何會出現,它解決了什麼問題, 以及背後的設計理念。html

正文

若是你有 Hooks 的使用經驗, 能夠思考一下這兩個問題:java

  1. Hooks 爲何會產生
  2. Hooks 解決了什麼問題

咱們先主要圍繞這兩點展開討論。react

1. Hooks 爲何會產生

在正式開始這個話題前, 咱們先回顧一下react的發家史.app

2013年5月13號, 在JS Conf 上發佈了第一個版本0.3.0.async

我第一次接觸React 是在2015年, 對createClass語法記憶猶新:ide

image.png

createClass 是當時第一種用於建立 React 組件的語法, 由於當時Javascript 尚未成形的 Class 體系。函數

這種狀況在2015年1月17號獲得了改變。post

這時候, ES6 正式發佈,支持 Class 語法。ui

這時候面臨一個選擇:

繼續用自家的 createClass 呢 仍是使用新的 ES6 Class

畢竟 createClass 又不是不能用, 並且用着還挺順手。

最後, React 仍是選擇了擁抱新趨勢, 使用ES6 Class。

並在 React 0.13.1 Beta1版本, 開始支持使用原生Javasciprt Class語法。 我找到了以下說明:

image.png

大意就是: 咱們並不想本身單獨搞一套, 你們習慣怎麼用, 咱們就怎麼搞。

基於這個變化, React Class 組件就變成了咱們以前常常見到的這樣:

image.png

是否是很熟悉。

生命週期方法和以前保持一致,變化的是組件初始化的部分。

從本來的getInitinalState 變成了constructor

經歷過這個階段的小夥伴確定對如下代碼段很是熟悉:

constructor(props) {
  super(props); // 🤮🤮🤮
  
  this.state = {};
  
  this.xxx = this.xxx.bind(this); // 😢😢😢
}

在組件初始化的時候, 必須手動super(props) 一下, 至於爲何這麼作, 本文不作討論, 有興趣的能夠看一下這篇譯文: 爲何要寫Super(props)

Class Fields 也容許咱們跳過這一步:

class xxx extends React.Component { 
  state = {};
}

到這一步, 已經解決了兩個使人難受的點:

  1. super
  2. bind

已經足夠OK了, 是吧。

其實還不夠。

咱們在編寫react 應用的時候, 難以免的一件事就是: 拆分react 組件。

把一個複雜的UI視圖拆分成不一樣的模塊, 而後組合在一塊兒。

這也是 react 自己推崇的理念: 萬物皆但是組件。

這種設計很棒棒, 但依舊有不少問題。

我認爲主要是亮點:

  1. 組件內邏輯的割裂
  2. 邏輯複用困難

1. 先說 邏輯上的割裂

基於生命週期的設計, 使得咱們常常寫出邏輯割裂的代碼:

image.png

一樣的邏輯, 咱們須要在不一樣的生命週期中去實現。

在一個大型的app 中, 相似的邏輯會有不少, 摻雜在一塊兒。

後人要去修改的時候, 不得不使用上下左右反覆橫跳之術, 使人十分痛苦。

2. 邏輯複用困難

咱們都知道, react 應用實際上是由一些列 UI 套件組合而成的, 這些套件有些有狀態, 有些沒有狀態。

image.png

把這些組件組合在一塊兒,處理好複用, 實際上是有必定困難的。

好比,假設在另一個組件,有和上圖類似的邏輯, 怎麼辦呢?

Copy & Paste 顯然是能夠的, 但卻不是最優雅的。

React 自己並不提供解決方案,可是機智的網友們逐漸摸索出了一些改善這個問題的方法:

  1. High Order Components
  2. Render Props

High Order Components 爲例, 看一下最簡的例子

爲組件都加入一個data屬性, 而後返回這個加強的組件:

image.png

邏輯並不複雜。

回到咱們最初的那個例子, 如今要把這部分邏輯抽離出來, 實現一個WithRepos 高階方法:

image.png

使用的時候, 包裹一下就能夠了:

image.png

Render Props 也是一樣的目的, 不做贅述, 可參考:Render Props

這兩種作法, 均可以改善邏輯複用的困境,但同時又引入了新的問題。

仍是以高級組件爲例, 好比咱們對一個組件要加入多個加強功能,顯而易見, 代碼就變成了:

export default withA(
  withB (
     withC (
        withD (
           Component
        )
     )
  )
)

Render Props 也同樣, 這兩種模式都會限制你的組件結構,隨着功能的增長, 包裹的層數愈來愈多,陷入所謂的 wrapper hell之中。

這種狀況並非咱們想要的。

寫到這裏, 咱們進行一個簡單的總結, 整理一下遇到的問題:

  1. 咱們使用 Class 語法來生成組件,super語法很煩, 可是能夠跳過。
  2. this 讓人懵逼。
  3. 基於生命週期的設計, 容易形成邏輯上的割裂, 不容易維護。
  4. React 沒有之後好的模式來解決邏輯複用問題。

因此, 迫切須要一種新的模式來解決以上這些問題。

理想中, 這種模式要具有如下特色:

  1. 簡單好用
  2. 足夠靈活
  3. 方便組合
  4. 擴展性強

那麼, 這種新的模式該如何設計呢?

此處引用一下John Carmack的話:

image.png

並且, Javascript 自己對 function 也是天生友好。

因此, 這時候要作的就是:

  • 拋棄 React.Component
  • 擁抱 function

image.png

在這個背景下, Hooks 應運而生。

2. Hooks 解決了什麼問題

擁抱 Function, 面前就有三座大山須要解決:

  1. 組件 State
  2. 生命週期
  3. 邏輯複用難題

2.1 State

image.png

State Hook 的標準形式是返回一個元組, 包含兩個元素:

image.png

使用起來也很是的簡單:

image.png

至此,有了state hook, function 就具有了基礎的狀態管理能力:

  1. 組件 State ✅
  2. 生命週期
  3. 邏輯複用難題

2.2 Lifecyles

這一步, 咱們先忘記傳統 Class Component 的生命週期方法, 想一下, 如何在 Function 中實現相似的能力。

咱們要用這樣的能力去實現,好比:

  1. 狀態變動
  2. 數據獲取
  3. 等等

基於這樣的思考, useEffect 問世了。

useEffect 賦予了Function 在組件內部執行反作用的能力。

image.png

就形式而言, Effect Hook 接受兩個參數:

  1. 一個 function.
  2. 一個可選的 array.

簡單的例子:

image.png

當 username 變化時, 就修改document title.

⚠️ 注意

有時候,你也許會不經意間把 Effect 寫成一個 async 函數:

強烈建議你不要這樣作。

useEffect 約定:

Effect 函數 要麼沒有返回值要麼返回一個 Cleanup 函數

而這裏 async 函數會隱式地返回一個 Promise,直接違反了這一約定,會形成不可預測的結果

至此, Function 組件也有了應該具有的生命週期方法。

  1. 組件 State ✅
  2. 生命週期 ✅
  3. 邏輯複用難題

只剩最後一個課題: 邏輯複用。

2.3 Sharing Non-Visual Logic

傳統而言, 咱們把頁面拆分紅一個個UI組件, 而後把這個UI組件組合起來。 這種狀況最終也不可避免的誕生了 HOC & Render Props 等模式來改善邏輯複用問題。

你可能會想, React Hooks 可能會有新的解決辦法。

辦法的確是有, 它就是Custom Hooks.

你能夠把須要複用的邏輯抽成一個個單獨的Custom Hook, 在須要用的地方使用。

舉個例子:

把須要複用的邏輯抽離:

image.png

在須要用到的地方使用:

image.png

這樣, 咱們就輕鬆而又天然的實現了邏輯的複用。

  1. 組件 State ✅
  2. 生命週期 ✅
  3. 邏輯複用難題 ✅

至此, 三個難題得以解決。

Hooks 的價值所在

回頭咱們再看這個問題, 其實從始至終, 要解決的問題只有一個:

提高代碼複用以及組合的能力。

順帶的, 也必定程度上提高了組件的內聚性, 減小了維護成本:

相關的邏輯都在單獨的一塊, 改需求的時候,不用須要施展上下左右反覆橫跳之術,提前了下班時間, 多好。

結尾

知其然,也要知其因此然。

咱們在本身平時的搬磚運動中, 也要考慮本身的代碼是否具有一下能力:

  1. 簡單好用
  2. 足夠靈活
  3. 方便組合
  4. 擴展性強

不坑本身, 也不坑別人, 早點下班。

好了, 別的就不扯了, 但願這篇文章能給你一些啓發和思考。

才疏學淺, 文章如有錯誤, 歡迎留言之正。

相關文章
相關標籤/搜索