跳到主要內容
黯羽輕揚每天積累一點點

React Hooks 簡介

免費2026-03-05#Front-End#react hooks target#react hooks motivation#React Hooks评价#React Hooks意义

一、出發點

在 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 等核心特性更激動人心的

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論