一、出發點
在 React 現有的組件模型下,存在很多難以解決的問題:
-
難以跨組件複用狀態邏輯
-
組件複雜度高難以理解
-
Class 的諸多弊病
-
……
而 Hooks,肩負著破局使命
組件間邏輯複用
組件間邏輯複用一直是個問題,Render Props、Higher-Order Components 等常用套路模式都是為了分離橫切關注點(Cross-cutting concern),複用諸如:
-
日誌
-
緩存/同步/持久化
-
數據校驗
-
錯誤捕獲/異常處理
的邏輯,目的是將橫切關注點與核心業務邏輯分離開,以便專注於業務邏輯
P.S. 關於切面、關注點等 AOP 概念的更多信息,見 AOP(Aspect-Oriented Programming)
然而,HOC 與 Render Props 雖然能以組件形式分離橫切關注點,但也帶來了一些新問題:
-
擴展性限制
-
Ref 傳遞問題
-
Wrapper Hell
之所以會出現這些問題,根本原因在於:
細粒度代碼複用不應該與組件複用捆綁在一起
而一直以來都缺少一種簡單直接的組件行為擴展方式:
React doesn't offer a way to "attach" reusable behavior to a component (for example, connecting it to a store).
提出 Hooks 的主要目的就是為了解決這個問題:
React needs a better primitive for sharing stateful logic.
P.S. 關於組件間邏輯複用方式的更多信息,見 [React 組件間邏輯複用](/articles/react 組件間邏輯複用/)
組件複雜度問題
如你所見,React 組件正在變得越來越複雜:
Provider, Consumer, Higher-order Component, Render Props
// with Redux
Action, Reducer, Container
// with Reselect
Selector
// with xxx ...
等諸多抽象層緩解了狀態邏輯的組織和複用問題,但隨之而來的問題是組件複用成本更高了,不再是簡單地引入組件就能獲得完整的業務功能
誠然,這些細分抽象層能讓代碼職責變得更加清晰,但狀態邏輯也被打散了,難以複用。因而組件複用程度大多停留在 View Component (View + UI State Logic) 層面,而無法更進一步複用 Business Component (View + Business State Logic)。除非將這些業務狀態邏輯(請求數據、訂閱其它數據源、定時器等)全都收攏到單一組件裡(比如改用 mobxjs/mobx 管理狀態)。即便這樣,也無法避免組件中摻雜著的副作用,以及生命週期方法中混在一起的不相干的邏輯,比如 componentDidMount 裡含有數據請求、事件監聽等……我們發現,真正有內在關聯的代碼被生命週期拆開了,而完全不相干的代碼最終卻湊到了一個方法裡:
Mutually related code that changes together gets split apart, but completely unrelated code ends up combined in a single method.
按組件生命週期拆分邏輯讓組件體積迅速膨脹,而且這種巨大組件不容易拆成一堆小組件,因為狀態邏輯到處都有,還難以測試
��此,需要一種更合理的、更容易複用的狀態邏輯組織方式,讓有內在關聯的代碼聚在一起,而不是被生命週期方法強制拆開
P.S. 關於 MobX 的更多信息,見 MobX
Class 弊病
Class 作為對象模具,是 OOP 中相當重要的一部分。然而,用 Class 來定義(視圖與邏輯相結合的)組件卻不那麼理想:
-
讓代碼難以組織/複用
-
帶來更高的學習成本
-
阻礙編譯優化
代碼組織/複用方面,Class 的生命週期方法是典型的例子,一個組件只能存在一個特定的生命週期方法,因而只能將一些不相干的邏輯放在一起,並且這種 模板式的劃分 讓具有內在關聯的代碼被拆開了,不利於代碼複用
學習成本上,主要體現在兩點:
-
要理解 JavaScript 中的
this(與其它語言不一樣),並記著bind(this) -
理解函數式組件與 Class 組件的區別,及各自的應用場景
編譯優化方面,Class 讓一些工具優化效果大打折扣,例如:
-
組件提前編譯(ahead-of-time compilation)效果不理想(React 團隊已經在這方面做了 一些嘗試,發現 Class 組件不利於編譯優化)
-
Class 不利於代碼壓縮
-
難以正確熱重載(hot reloading)
P.S. 組件提前編譯類似於 [GCC 的高級模式](/articles/react 背後的工具化體系/#articleHeader11),對 defaultProps 常量等進行內聯優化,並去除無用代碼
因此,希望提供一套編譯優化友好的 API:
We want to present an API that makes it more likely for code to stay on the optimizable path.
所以拋棄 Class,擁抱函數:
Hooks let you use more of React's features without classes.
P.S. 並非轉投函數式編程,全面引入 FP 概念,而是提供了通向命令式編程的「逃生艙」,而不必掌握函數式編程、響應式編程等技術:
Hooks provide access to imperative escape hatches and don't require you to learn complex functional or reactive programming techniques.
二、目標
為了解決以上種種問題,Hooks 應運而生,目標是:
-
提供一種簡單直接的代碼複用方式
-
提供一種更合理的代碼組織方式
-
提供一種 Class 的替代方案
一方面解決代碼組織、複用的問題,另一方面,新的組件定義方式也是 React 未來願景的一部分:
Hooks represent our vision for the future of React.
那麼,Hooks 到底是個什麼東西?
三、定位
Hooks 是一些能讓函數式組件接入 React State 和生命週期等特性的函數:
Hooks are functions that let you "hook into" React state and lifecycle features from function components.
一方面藉助 Hooks 更合理地拆分/組織代碼,解決複用問題,另一方面通過 Hooks 增強函數式組件,讓其擁有與 Class 組件相同的表達力,進而成為一種替代選項,最終取而代之
四、作用
Hooks 主要解決了代碼組織、邏輯複用方面的問題,例如:
-
組織被生命週期拆開的關聯邏輯,如數據源訂閱/取消訂閱、事件監聽註冊/註銷等
-
跨組件複用散落在生命週期中的重複邏輯,同時解決 HOC 和 Render Props 等基於組件組合的複用模式帶來的組件嵌套問題(Wrapper Hell)
此外,對 React 自身而言,Hooks 還解決了大規模優化上的阻礙,比如內聯組件的編譯難題
代碼組織
Hooks 方案下,最大的區別在於,可以將組件基於代碼塊的內在關聯拆分成一些小函數,而不是強制按照生命週期方法去拆分:
Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.
例如:
// 自定義 Hook
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// 註冊/註銷外部數據源
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
註冊/註銷外部數據源的代碼緊密地聯繫在一起,而不用再關心調用時機(組件生命週期)的差異
邏輯複用
同時,如上面示例,這些狀態邏輯和副作用能被輕鬆抽離到 Hooks 中,並組合成 Custom Hook,漂亮地解決了狀態邏輯的複用問題:
With Hooks, you can extract stateful logic from a component so it can be tested independently and reused.
例如:
// View 組件 1
function FriendStatus(props) {
// 使用自定義 Hook
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
// View 組件 2
function FriendListItem(props) {
// 使用自定義 Hook
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
另一方面,這種複用方式是「無傷的」,不必調整組件樹層級結構,即引即用:
Hooks allow you to reuse stateful logic without changing your component hierarchy. This makes it easy to share Hooks among many components or with the community.
五、總結
單從形式上看,Hooks 是對函數式組件的增強,使之能與類組件平起平坐,甚至(期望)取而代之。實質意義在於進一步將更多的函數式思想引入到前端領域,比如 Effect、Monad 等。算是在提出 v = f(d) 的 UI 層函數式思路之後,在這條路上的進一步探索
(摘自 React 16 Roadmap)
從某種程度上來講,這種思想風暴是比 Concurrent Mode 等核心特性更激動人心的
暫無評論,快來發表你的看法吧