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

React Hooks 簡介

免費2019-06-08#Front-End#react hooks target#react hooks motivation#React Hooks评价#React Hooks意义

Hooks 是個什麼東西?有什么用?

一、出發點

在 React 現有的組件模型下,存在很多難以解決的問題:

  • 難以跨組件複用狀態邏輯

  • 組件複雜度高難以理解

  • Class 的諸多弊病

  • ……

而 Hooks,肩負著破局使命

組件間邏輯複用

組件間邏輯複用一直是個問題,Render PropsHigher-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 等核心特性更激動人心的

參考資料

評論

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

提交評論