React Hooks 是什么?

2022年11月16日

💎 加入 E+ 成長計畫 與超過 350+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

虽然近几年大部分开发 React 都是使用 Hooks,但 Hooks 是在 React 16.8 之后出现的,在面试时也会被问到 Hooks 的相关问题,本篇文章总结了

  • 什么是 Hook?
  • Hook 解决了什么问题?
  • Hook 的规则?
  • 常使用的 Hook?

什么是 Hook?

在 React 16.8 之前,如果我们想要使用到生命周期的方法或状态 (state),那我们只能使用 React 的 class component。但 React hooks 的出现,让我们即使使用 functional component,也能够使用到 React 的功能和状态。在 React 中,用 use 开头的函式我们会把它称之为 Hook,有些 Hooks 是 React 内建的功能,例如:useStateuseEffect,但我们也可以创造自己的 Hooks。 Hooks 相较于函式更严谨、有一些需要遵守的规范,我们接下来在下方会提到。

Hook 解决了什么问题?

React 团队开发 Hooks 主要为了解决这三个原因

  • 状态相关的逻辑 (stateful logics) 在 class component 之间难以复用

    在使用 class component 时,React 并没有提供可以将重复的逻辑添加到元件的方法。因此,开发者可能使用 render props 或是 higher-order components 的方式达到,但这个缺点会是需要重新架构元件,不仅麻烦、而且代码也不易读 (React 文件戏称为 wrapper hell)。因此, Hook 的解决的主要问题之一,正是让共享状态逻辑。

    使用 Hooks,开发者可以从元件中提取状态相关的逻辑,并对它进行独立复用。我们也不需要重组元件,就可以在元件中复用相同逻辑。举例来说,如果某个应用程式需要侦测页面滑动,并根据滑动来触发某些函式,这时我们可以写一个 useScroll,并在不同页面共享这个 useScroll 的状态逻辑。目前在 React 的社群中,有很多 Hooks 的函式库,便提供了各式各样可重复使用的 Hooks。

  • 在 Hooks 出现前,复杂元件的逻辑会越写越让人难理解

    当 class component 越来越庞大或逻辑复杂时,我们可能会在同一个生命周期方法内需要加入很多不相关的逻辑,例如:componentDidMount 要处理 data fetching 和事件侦听器逻辑,不仅难以理解也不好维护,且在很多情况下,也无法把这些元件拆成更小的元件。

    为了解决此问题, Hooks 允许将一个元件拆为更小的函式,而不是根据生命周期的方法拆分,例如上段例子,data fetching 和事件侦听器两种逻辑,可以在同一个元件,通过两个useEffect 各自处理,又或者两者拆成两个 custom hooks。透过这样细分,,不相关的逻辑可以拆到不同的 useEffect,让副作用的逻辑更好管理。

  • class 对于开发者来说不好理解

    React 团队发现,class 可能会是学习 React 的主要障碍。因为 class 的概念在 JavaScript 和其他语言中相当不同 (在 JavaScript 当中,class 是语法糖),如果开发者过去是写其他语言,转来写 JavaScript 时,需要特别了解 class 在 JavaScript 是如何运作的。

    为了解决这些问题,Hooks 可以在没有 class 的情况下使用更多 React 的特性。从概念上讲,React 元件一直更接近函式。 Hooks 包含函式,但不牺牲 React 的实用精神,且不需要特别学习复杂的 functional or reactive programming。

Hook 的规则

  1. 只能在最顶层使用 Hook

    不能在for 循环、if…else 或巢状中(如: map )中使用 Hook,我们需要确保永远都在 React 函式的最顶层以及任何 return 之前使用(备注:在{% outerLink href="https://www.explainthis.io/zh-hans/swe/why-call-react-hook-top-level" text="《为什么只能在最顶端层呼叫 Hook?》" /% }一文当中,我们有详细讨论原因,这也是常考的面试题,推荐大家多读读)。

  2. 只能在 React 函式中使用 Hook

    无法在一般的 JavaScript 函式中使用 Hook,只有以下两种情境可以使用

    • 在 React 的 functional component 中使用
    • 在自定义的 Hook (custom hook) 中使用其他 Hook

常使用的 Hook

  • useState:用于定义和保存元件中状态(state),会回传一个包含两个值的数组,第一个值是现在 state 的值,第二个值是一个 setter function,我们可以透过这个 setter function 更新 state 值并触发 re-render。
  • useEffect:当想执行副作用(side effect)时,会透过 useEffect 处理,例如:fetch api、纪录追踪、setInterval()。我们需要传送两个参数在useEffect 中,第一是一个 setup function,代表我们想执行的副作用代码,如果最后我们要清除这个副作用,需要在最后回传一个 cleanup function; 第二个参数会是一个数组,数组中的元素会是相依于这个副作用代码会用到的变数。
  • useLayoutEffect:与 useEffect 是类似的,唯一不同点在于执行时机不同,useLayoutEffect 会在 DOM 更新之后执行; useEffect 则是在 render 渲染结束后执行。
  • useReducer:也是一种管理 state 的 hook,可作为 useState 替代方案。接收两个参数,第一个是 reducer,第二个为初始值。会返回两个值,现在 state 的值和 dispatch   方法。当状态管理逻辑变得更复杂时,通常会建议使用 useReducer 而非 useState
  • useCallback:在重新渲染之间,用来缓存函式的方法。会回传一个被 memoized 的 callback 函式,只有在依赖发生变化时,才会更新。此方法通常用在性能优化时。
  • useMemo:在重新渲染之间,用来缓存计算结果的方法。传入一个创建函式和依赖项目,创建函式会需要返回一个值,只有在依赖发生变化时,才会更新值。此方法通常用在性能优化时。
  • useRef:用来储存记录不需要渲染的值。会返回一个可变的 ref 物件,.current 属性会被初始化为传入的参数值(initialValue)。
🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們