背景
React Hooks 已經出來有段時間了, 很多小夥伴或多或少都用過。
今天呢,我們就回頭再看一下這個東西,思考一下,這個東西為什麼會出現,它解決了什麼問題, 以及背後的設計理念。
正文
如果你有 Hooks 的使用經驗, 可以思考一下這兩個問題:
- Hooks 為什麼會產生
- Hooks 解決了什麼問題
我們先主要圍繞這兩點展開討論。
1. Hooks 為什麼會產生
在正式開始這個話題前, 我們先回顧一下react的發家史
.
2013年5月13號, 在JS Conf 上釋出了第一個版本0.3.0
.
我第一次接觸React 是在2015年, 對createClass語法記憶猶新:
createClass
是當時第一種用於建立 React 元件的語法, 因為當時Javascript 還沒有成形的 Class 體系。
這種情況在2015年1月17號得到了改變。
這時候, ES6 正式釋出,支援 Class 語法。
這時候面臨一個選擇:
繼續用自家的 createClass
呢 還是使用新的 ES6 Class
?
畢竟 createClass 又不是不能用, 而且用著還挺順手。
最後, React 還是選擇了擁抱新趨勢, 使用ES6 Class。
並在 React 0.13.1 Beta1
版本, 開始支援使用原生Javasciprt Class語法。 我找到了如下說明:
大意就是: 我們並不想自己單獨搞一套, 大家習慣怎麼用, 我們就怎麼搞。
基於這個變化, React Class 元件就變成了我們之前經常見到的這樣:
是不是很熟悉。
生命週期方法和之前保持一致
,變化的是元件初始化
的部分。
從原本的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 = {};
}
到這一步, 已經解決了兩個令人難受的點:
superbind
已經足夠OK了, 是吧。
其實還不夠。
我們在編寫react 應用的時候, 難以避免的一件事就是: 拆分react 元件。
把一個複雜的UI檢視拆分
成不同的模組, 然後組合
在一起。
這也是 react 本身推崇的理念: 萬物皆可是元件。
這種設計很棒棒, 但依舊有很多問題。
我認為主要是亮點:
元件內邏輯的割裂
邏輯複用困難
1. 先說 邏輯上的割裂
:
基於生命週期的設計, 使得我們經常寫出邏輯割裂
的程式碼:
同樣的邏輯, 我們需要在不同的生命週期中去實現。
在一個大型的app 中, 類似的邏輯會有很多, 摻雜在一起。
後人要去修改的時候, 不得不使用上下左右反覆橫跳之術
, 令人十分痛苦。
2. 邏輯複用困難
我們都知道, react 應用其實是由一些列 UI 套件組合而成的, 這些套件有些有狀態, 有些沒有狀態。
把這些元件組合在一起,處理好複用, 其實是有一定困難的。
比如,假設在另外一個元件,有和上圖相似的邏輯, 怎麼辦呢?
Copy & Paste
顯然是可以的, 但卻不是最優雅的。
React 本身並不提供解決方案,但是機智的網友們
逐漸摸索出了一些改善這個問題的方法:
High Order Components
Render Props
以High Order Components
為例, 看一下最簡的例子
為元件都加入一個data屬性, 然後返回這個增強的元件:
邏輯並不複雜。
回到我們最初的那個例子, 現在要把這部分邏輯抽離出來, 實現一個WithRepos
高階方法:
使用的時候, 包裹一下就可以了:
Render Props 也是同樣的目的, 不作贅述, 可參考:Render Props
這兩種做法, 都可以改善邏輯複用的困境,但同時又引入了新的問題。
還是以高階元件為例, 比如我們對一個元件要加入多個增強功能,顯而易見, 程式碼就變成了:
export default withA(
withB (
withC (
withD (
Component
)
)
)
)
Render Props 也一樣, 這兩種模式都會限制你的元件結構,隨著功能的增加, 包裹的層數越來越多,陷入所謂的 wrapper hell
之中。
這種情況並不是我們想要的。
寫到這裡, 我們進行一個簡單的總結, 整理一下遇到的問題:
- 我們使用 Class 語法來生成元件,super語法很煩, 但是可以跳過。
- this 讓人懵逼。
- 基於生命週期的設計, 容易造成邏輯上的割裂, 不容易維護。
- React 沒有以後好的模式來解決邏輯複用問題。
所以, 迫切需要一種新的模式
來解決以上這些問題。
理想中, 這種模式要具備以下特點:
- 簡單好用
- 足夠靈活
- 方便組合
- 擴充套件性強
那麼, 這種新的模式
該如何設計呢?
此處引用一下John Carmack的話:
而且, Javascript 本身對 function 也是天生友好。
所以, 這時候要做的就是:
- 拋棄 React.Component
- 擁抱 function
在這個背景下, Hooks 應運而生。
2. Hooks 解決了什麼問題
擁抱 Function, 面前就有三座大山需要解決:
- 元件 State
- 生命週期
- 邏輯複用難題
2.1 State
State Hook 的標準形式是返回一個元組, 包含兩個元素:
使用起來也非常的簡單:
至此,有了state hook, function 就具備了基礎的狀態管理能力:
- 元件 State ✅
- 生命週期
- 邏輯複用難題
2.2 Lifecyles
這一步, 我們先忘記傳統 Class Component 的生命週期方法, 想一下, 如何在 Function 中實現類似的能力。
我們要用這樣的能力去實現,比如:
- 狀態變更
- 資料獲取
- 等等
基於這樣的思考, useEffect 問世了。
useEffect 賦予了Function 在元件內部執行副作用的能力。
就形式而言, Effect Hook 接受兩個引數:
- 一個 function.
- 一個可選的 array.
簡單的例子:
當 username 變化時, 就修改document title.
⚠️ 注意
有時候,你也許會不經意間把 Effect 寫成一個 async 函式:
強烈建議你不要這樣做。
useEffect 約定:
Effect 函式要麼沒有返回值
,要麼返回一個 Cleanup 函式
。
而這裡 async 函式會隱式地返回一個 Promise,直接違反了這一約定,會造成不可預測的結果
。
至此, Function 元件也有了應該具備的生命週期方法。
- 元件 State ✅
- 生命週期 ✅
- 邏輯複用難題
只剩最後一個課題: 邏輯複用。
2.3 Sharing Non-Visual Logic
傳統而言, 我們把頁面拆分成一個個UI元件, 然後把這個UI元件組合起來。 這種情況最終也不可避免的誕生了 HOC & Render Props 等模式來改善邏輯複用問題。
你可能會想, React Hooks 可能會有新的解決辦法。
辦法的確是有, 它就是Custom Hooks
.
你可以把需要複用的邏輯抽成一個個單獨的Custom Hook
, 在需要用的地方使用。
舉個例子:
把需要複用的邏輯抽離:
在需要用到的地方使用:
這樣, 我們就輕鬆而又自然的實現了邏輯的複用。
- 元件 State ✅
- 生命週期 ✅
- 邏輯複用難題 ✅
至此, 三個難題得以解決。
Hooks 的價值所在
回頭我們再看這個問題, 其實從始至終, 要解決的問題只有一個:
提升程式碼複用以及組合的能力。
順帶的, 也一定程度上提升了元件的內聚性, 減少了維護成本:
相關的邏輯都在單獨的一塊, 改需求的時候,不用需要施展上下左右反覆橫跳之術,提早了下班時間, 多好。
結尾
知其然,也要知其所以然。
我們在自己平時的搬磚運動中, 也要考慮自己的程式碼是否具備一下能力:
- 簡單好用
- 足夠靈活
- 方便組合
- 擴充套件性強
不坑自己, 也不坑別人, 早點下班。
好了, 別的就不扯了, 希望這篇文章能給你一些啟發和思考。
才疏學淺, 文章若有錯誤, 歡迎留言之正。