React 16.7 的 Hooks 為何讓人眼睛一亮

C.T. Lin
YOCTOL.AI
Published in
7 min readNov 6, 2018
“three hanged assorted-color ceramic mugs” by Chris Scott on Unsplash

前幾天 React 核心團隊在 React Conf 2018 提出了新的 Proposal — React Hooks。是近期 React 除了 Concurrent React 外,另一個令人非常興奮的功能。

如果你是 React 的開發者的話,非常建議觀看這部影片,應該能得到很好的啟發:

用一句話來描述,Hooks 的目的就是讓 Function Component 能夠用更簡單直覺地方式去使用 React 的功能。

雖然 Hooks 還只是個 Proposal,但已經實作在 16.7.0-alpha 之中並在 Facebook 內部實驗了一個月的時間,而且還提供了非常完整的官方文件

我們來看一下幾個基本的 Hooks 範例:

useState

以往,只要需要用到 State 就必須寫成 Class Component 來掌管 State,useState 則提供我們寫 Stateful Function Component 的機會。

useState 這個 Hook 接收一個 initialState 回傳 [value, setValue]

在一個 Component 之中可以宣告多個 State,如此一來,就不再需要把各種無關的 State 硬是列在同一個 Object 上,操作 setState 時也不用同時考慮所有狀態該如何調整了。

useContext

React 16.3 提供了基於 Render Props 的新 Context API,在 Component 裡面必須使用 Consumer 來獲取 Context 裡的資料。

這種做法添加了許多不必要的階層,在添加跟重構時常常要搬來搬去,巢狀比較深的時候也明顯影響到可讀性。

useContext 則是接收一個 Context 然後直接回傳 Context 裡的資料,使用上更加簡潔。

有點類似從 Callback 來到 Async/Await 的感覺

Hooks 解決了什麼問題?

在 Component 之間共用行為

最早這個問題是由 React 提供 Mixin 來解決,在 Mixin 因為一些缺點被移除後,React 團隊與社群則開始推崇 Higher-Order Component (HoC),以及近一年非常火紅的 Render Props。而這些作法也各自帶來了一些問題:

Higher-Order Component

  • 不同 HoC 傳遞的 props 會有命名衝突的風險
  • 被包了一層的 Component 無法從外面讀取 static 屬性

Render Props

  • 程式碼縮排產生 Callback Hell
  • 影響 Component 原先的階層性

這兩種做法同時也都有讓人頭痛的深層巢狀問題:

是否常常不知道要往右滑多少才能在 React devtools 看到自己的 Component 呢

因為 State 而必須轉換成 Class Component

假設我們有一個實做到一半的 Counter Component:

寫到這裡,開始覺得需要有 state 來控制這一切,如果我們狠下心把它轉成 Class,大概會是這樣子:

改寫的幅度頗大

只是為了一個 State,需要這麼大的改寫幅度,如果不使用 Class Properties 的語法還需要把 Method 都 bind this。

那我們再來看看使用 Hooks 的版本:

這個寫法幾乎只是在前面加個幾行,不需要大幅度的改寫就能使用 State。使用 Hooks 的話,不管 State、Context、Effect 都可以在相對比較小的幅度下完成修改。

大幅提昇 Component 的壓縮能力

雖然 React 的 Size 稍微提升,但由於避開了 Class Component,App bundle 的 Size 則會大幅降低,另外減少依賴 Object,也讓壓縮時能 rename 的變數更多了。

useEffect 能把 Lifecycle 邏輯整理得更好

以往,Component Lifecycle 只能在 Class Component 中去控制:

相關的程式碼被分散在很多不同的 Lifecycle 裡

這對熟悉 React 的開發者可能不是什麼大問題,但對初學者來說卻是常見造成 Bug 的原因。如果無法快速了解 componentWillMount、
componentDidMount、componentWillReceiveProps、
componentDidUpdate、componentWillUnmount 的使用時機,很容易就會寫出錯誤的判斷式,或因為疏忽了某個步驟造成了記憶體洩漏。

而 useEffect 這個 Hook 則把常見的用法整理成一個 API,提供一個執行 Effect 的 Function,用以取代傳統的 componentDidMount、componentDidUpdate,並讓這個 Function 回傳清除 Effect 的步驟:

subscribe、unsubscribe 這兩個相關的部分被整理在一起

useEffect 的第二個參數 inputs,則是用來判斷 Effect 是否需要重新執行,跟以往常見在 componentWillReceiveProps 與 componentDidUpdate 中所執行的 props、state 比對類似。

除了 useEffect 以外,useMemouseCallback 等等的 Hooks 也都同樣是靠這個 inputs 參數來決定是否需要重新執行。

如此一來,就可以依照 Effect 來整理程式碼,不需要依照 Lifecycle 整理程式碼,可以達到關注點分離。

Hooks 如何儲存 State

因為每次 render 都會呼叫 useState,許多人可能會好奇 Hooks 是如何儲存並拿到同一個 State 的。

實際上它並沒有使用太複雜的實作方式,只是第一次 render 會建立資料結構來儲存,並在之後 render 時使用 cursor 去逐一取用。

下面這張圖是出自「Making Sense of React Hooks」一文中的超級簡化版,直接利用 Array 來示意。另外,這篇文中也有很詳盡的介紹。

出處:https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889

也因為實作細節如此,順序才會如此的重要。所以不能把 Hooks 放在任何的 Condition 裡面

然而實際上 Hooks 跟 Fiber 一樣大量地採用了 Linked List,如果想一探究竟可以參考這份原始碼,大部分 Hooks 的實作細節都在這個檔案裡。

總結

雖然還只是個 Proposal,但 React Hooks 在社群引發了很大的討論,也帶動催生了一些基於 Hooks 的 Library 例如:the-platformreact-use 等等。

如果 Hooks 能經得起時間考驗的話,基於 HoC 或是 Render Props 的做法可能會逐漸減少,影響到 recomposereact-powerplug 等等相關的 Library。

另一個值得注意的點是,React 內建了 useReducer 這個 Hook,更進一步的推廣 Dan Abramov 在 「You Might Not Need Redux
一文中所提到的 Component 內置 Reducer 的模式。這也會讓 Redux 的重要性再更一步地降低。

接著讓我們期待正式版盡快推出吧。

參考資料

優拓資訊持續招募熱愛技術的軟體工程師,更多詳細職缺資訊請見官網招募頁面

--

--

C.T. Lin
YOCTOL.AI

Architect @ Dcard. Author of Electron React Boilerplate and Bottender. JavaScript Developer.