React Hooks 的最佳实践
已发表: 2022-03-10 React Hooks 是 React 16.8 中的新增功能,它让您无需编写class
组件即可使用状态和其他 React 功能。 换句话说,Hook 是让你从函数组件中“挂钩”到 React 状态和生命周期特性的函数。 (它们在class
组件中不起作用。)
React 提供了一些内置的 Hooks,比如useState
。 您还可以创建自己的 Hooks 以在不同组件之间重用有状态行为。 下面的示例显示了一个计数器,其状态使用useState()
挂钩进行管理。 每次单击按钮时,我们都会使用setCount()
将count
的值更新1
。
请参阅 Adeneye Abiodun David 的 Pen [React Hook example with Counter](https://codepen.io/smashingmag/pen/QWbXMyM)。
此示例呈现一个值为0
的计数器。 当您单击按钮时,它会将值增加1
。 组件的初始值是使用useState
定义的。
const [count, setCount] = useState(0)
如您所见,我们将其设置为0
。 然后当我们想要增加值时,我们使用onClick()
方法调用setCount
。
<button onClick={() => setCount(count + 1)}> Click me </button>
在 React Hooks 发布之前,这个例子会使用更多的代码行,因为我们不得不使用一个class
组件。
React Hooks 的规则
在深入探讨最佳实践之前,我们需要了解 React Hooks 的规则,这也是本文介绍的实践的一些基本概念。
React Hooks 是 JavaScript 函数,但是在使用它们时需要遵循两个规则。
- 在顶层调用 Hooks;
- 仅从 React 组件调用 Hooks。
注意:这两个规则是在 React Hooks 中引入的,而不是 JavaScript 本身的一部分。
让我们更详细地了解这些规则。
在顶层调用 Hooks
不要在循环、条件或嵌套函数中调用 Hook。 始终在 React 函数的顶层使用 Hooks。 通过遵循此规则,您可以确保每次渲染组件时都以相同的顺序调用 Hook。 这就是允许 React 在多个useState
和useEffect
调用之间正确保留 Hooks 状态的原因。
让我们创建一个有两种状态的Form
组件:
-
accountName
-
accountDetail
这些状态将具有默认值,我们将使用useEffect
挂钩将状态保存到浏览器的本地存储或文档的标题。
现在,如果该组件在多次调用useState
和useEffect
之间保持相同,则该组件可能会成功管理其状态。
function Form() { // 1. Use the accountName state variable const [accountName, setAccountName] = useState('David'); // 2. Use an effect for persisting the form useEffect(function persistForm() { localStorage.setItem('formData', accountName); }); // 3. Use the accountDetail state variable const [accountDetail, setAccountDetail] = useState('Active'); // 4. Use an effect for updating the title useEffect(function updateStatus() { document.title = accountName + ' ' + accountDetail; }); // ... }
如果我们的 Hooks 的顺序发生了变化(在循环或条件中调用它们时可能发生这种情况),React 将很难弄清楚如何保存我们组件的状态。
// ------------ useState('David') // 1. Initialize the accountName state variable with 'David' useEffect(persistForm) // 2. Add an effect for persisting the form useState('Active') // 3. Initialize the accountdetail state variable with 'Active' useEffect(updateStatus) // 4. Add an effect for updating the status // ------------- // Second render // ------------- useState('David') // 1. Read the accountName state variable (argument is ignored) useEffect(persistForm) // 2. Replace the effect for persisting the form useState('Active') // 3. Read the accountDetail state variable (argument is ignored) useEffect(updateStatus) // 4. Replace the effect for updating the status // ...
这就是 React 调用我们的钩子的顺序。 由于顺序保持不变,它将能够保留我们组件的状态。 但是如果我们在一个条件中加入一个 Hook 调用会发生什么呢?
// We're breaking the first rule by using a Hook in a condition if (accountName !== '') { useEffect(function persistForm() { localStorage.setItem('formData', accountName); }); }
accountName !== ''
条件在第一次渲染时为true
,所以我们运行这个 Hook。 但是,在下一次渲染时,用户可能会清除表单,从而使条件为false
。 现在我们在渲染过程中跳过了这个 Hook,Hook 调用的顺序变得不同了:
useState('David') // 1. Read the accountName state variable (argument is ignored) // useEffect(persistForm) // This Hook was skipped! useState('Active') // 2 (but was 3). Fail to read the accountDetails state variable useEffect(updateStatus) // 3 (but was 4). Fail to replace the effect
React 不知道第二个useState
Hook 调用返回什么。 React 期望这个组件中的第二个 Hook 调用对应于persistForm
效果,就像在之前的渲染中一样——但它不再是了。 从那时起,在我们跳过的Hook
调用之后的每个下一个 Hook 调用也会移动一个 - 导致错误。
这就是为什么必须在我们组件的顶层调用 Hooks。 如果我们想有条件地运行一个效果,我们可以把这个条件放在我们的 Hook 中。
注意:查看 React Hook 文档以阅读有关此主题的更多信息。
仅从 React 组件调用 Hooks
不要从常规 JavaScript 函数调用 Hooks。 相反,您可以从 React 函数组件中调用 Hooks。 下面我们来看看 JavaScript 函数和 React 组件的区别:
JavaScript 函数
import { useState } = "react"; function toCelsius(fahrenheit) { const [name, setName] = useState("David"); return (5/9) * (fahrenheit-32); } document.getElementById("demo").innerHTML = toCelsius;
这里我们从 React 包中导入useState
钩子,然后声明我们的函数。 但这是无效的,因为它不是 React 组件。
反应函数
import React, { useState} from "react"; import ReactDOM from "react-dom"; function Account(props) { const [name, setName] = useState("David"); return <p>Hello, {name}! The price is <b>{props.total}</b> and the total amount is <b>{props.amount}</b></p> } ReactDom.render( <Account total={20} amount={5000} />, document.getElementById('root') );
尽管两者的主体看起来相似,但当我们将 React 导入文件时,后者成为了一个组件。 这就是我们可以在内部使用 JSX 和 React 钩子之类的东西的原因。

如果你碰巧在没有导入 React 的情况下导入了你喜欢的钩子(这使它成为一个常规函数),你将无法使用你导入的 Hook,因为 Hook 只能在 React 组件中访问。
从自定义 Hooks 调用 Hooks
自定义 Hook 是一个 JavaScript 函数,其名称以use
开头,并且可以调用其他 Hook。 例如, useUserName
在调用useState
和useEffect
挂钩的自定义 Hook 下方使用。 它从 API 获取数据,遍历数据,如果它收到的特定用户名存在于 API 数据中,则调用setIsPresent()
。
export default function useUserName(userName) { const [isPresent, setIsPresent] = useState(false); useEffect(() => { const data = MockedApi.fetchData(); data.then((res) => { res.forEach((e) => { if (e.name === userName) { setIsPresent(true); } }); }); }); return isPresent; }
然后我们可以继续在应用程序中需要的其他地方重用这个钩子的功能。 在这样的地方,除非需要,我们不必再调用useState
或useEffect
了。
通过遵循此规则,您可以确保组件中的所有有状态逻辑从其源代码中清晰可见。
ESLint 插件
名为eslint-plugin-react-hooks
的 ESLint 插件强制执行上述规则。 在处理项目时,这在执行规则时非常方便。 我建议您在处理项目时使用此插件,尤其是在与他人合作时。 如果您想尝试,可以将此插件添加到您的项目中:
// Your ESLint configuration { "plugins": [ // ... "react-hooks" ], "rules": { // ... "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies } }
此插件默认包含在 Create React App 中。 因此,如果您使用 Create-React-App 引导您的 React 应用程序,则不需要添加它。
思考钩子
在深入探讨几个 Hooks 最佳实践之前,让我们简要了解一下class
组件和功能组件(使用 Hooks)。
在 React 中定义组件的最简单方法是编写一个返回 React 元素的 JavaScript 函数:
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
Welcome
组件接受props
,它是一个包含数据并返回 React 元素的对象。 然后我们可以在另一个组件中导入并渲染这个组件。
class
组件使用一种称为封装的编程方法,这基本上意味着与类组件相关的所有内容都将存在于其中。 生命周期方法( constructors
、 componentDidMount()
、 render
等)为组件提供了可预测的结构。
封装是OOP (面向对象编程)的基础之一。 它指的是在对数据进行操作的方法中捆绑数据,并用于隐藏类中结构化数据对象的值或状态——防止未经授权的各方直接访问它们。
使用 Hooks,组件的组合从生命周期 Hooks 的组合变为最终带有一些渲染的功能。
功能组件
下面的示例展示了如何在功能组件中使用自定义 Hooks(不展示主体是什么)。 但是,它所做的或可以做的并不受限制。 它可以是实例化状态变量、使用上下文、为组件订阅各种副作用——或者如果您使用自定义钩子,则可以是以上所有!
function { useHook{...}; useHook{...}; useHook{...}; return (
...); }
类组件
class
组件要求您从React.Component
扩展并创建一个返回 React 元素的render
函数。 这需要更多代码,但也会给您带来一些好处。
class { constructor(props) {...} componentDidMount() {...} componentWillUnmount() {...} render() {...} }
在 React 中使用函数式组件有一些好处:
- 分离容器组件和展示组件会变得更容易,因为如果您无法访问组件中的
setState()
,则需要更多地考虑组件的状态。 - 函数式组件更容易阅读和测试,因为它们是没有状态或生命周期钩子的纯 JavaScript 函数。
- 你最终得到的代码更少。
- React 团队提到,在未来的 React 版本中,功能组件的性能可能会有所提升。
这导致了使用 React Hooks 时的第一个最佳实践。
Hooks 最佳实践
1. 简化你的钩子
保持 React Hooks 简单将使您能够有效地控制和操纵组件在其整个生命周期中发生的事情。 尽可能避免编写自定义 Hooks ; 您可以内联useState()
或useEffect()
而不是创建自己的钩子。
如果您发现自己使用了一堆功能相关的自定义 Hook,您可以创建一个自定义 Hook 来充当这些 Hook 的包装器。 下面我们来看看两个不同的带有钩子的功能组件。
功能组件 v1
function { useHook(...); useHook(...); useHook(...); return( <div>...</div> ); }
功能组件 v2
function { useCustomHook(...); useHook(...); useHook(...); return( <div>...</div> ); }
v2 是一个更好的版本,因为它使钩子保持简单,并且所有其他useHook
都相应地内联。 这使我们能够创建可以在不同组件之间重用的功能,也使我们能够更有效地控制和操作我们的组件。 您应该使用 v2,而不是采用我们的组件中到处都是 Hooks 的 v1,这将使调试变得容易并且您的代码更清晰。
2. 组织和构建你的 Hooks
React Hooks 的优点之一是能够编写更少且易于阅读的代码。 在某些情况下, useEffect()
和useState()
的数量仍然可能令人困惑。 当您保持组件井井有条时,它将有助于提高可读性并保持组件流的一致性和可预测性。 如果您的自定义 Hooks 太复杂,您可以随时将它们分解为子自定义 Hooks。 将组件的逻辑提取到自定义 Hook 中,以使您的代码可读。
3. 使用 React Hooks 片段
React Hooks Snippets 是一个 Visual Studio Code 扩展,可让 React Hooks 更轻松、更快速。 目前,支持五个钩子:
-
useState()
-
useEffect()
-
useContext()
-
useCallback()
-
useMemo()
还添加了其他片段。 我尝试过使用这些 Hooks,这是我个人在使用它们时使用的最佳实践之一。
有两种方法可以将 React Hooks 片段添加到项目中:
- 命令
启动 VS Code 快速打开( Ctrl + P ),粘贴ext install ALDuncanson.react-hooks-snippets
并按Enter 。 - 扩展市场
启动“VS Code Extension Marketplace”( Ctrl + Shift + X )并搜索“React Hook Snippets”。 然后,寻找“Alduncanson”图标。
我推荐第一个片段。 在此处阅读有关片段的更多信息或在此处查看最新的 Hooks 片段。
4. 考虑 Hooks 规则
努力在使用 React Hooks 时始终考虑我们之前学到的 Hooks 的两条规则。
- 只在顶层调用你的 Hooks。 不要在循环、条件或嵌套函数中调用 Hook。
- 始终从 React 函数组件或自定义 Hooks 调用 Hooks,不要从常规 JavaScript 函数调用 Hooks。
名为eslint-plugin-react-hooks
的 ESlint 插件强制执行这两个规则,如果您愿意,可以将此插件添加到您的项目中,正如我们在上面的钩子规则部分中解释的那样。
最佳实践尚未完全解决,因为 Hooks 仍然相对较新。 因此,在采用任何早期技术时都应谨慎行事。 考虑到这一点,Hooks 是 React 未来的发展方向。
结论
我希望你喜欢这个教程。 我们已经了解了 React Hooks 的两个最重要的规则以及如何在 Hooks 中有效地思考。 我们研究了功能组件和一些以正确有效的方式编写 Hooks 的最佳实践。 尽管规则很简短,但在编写规则时让它们成为您的指南针很重要。 如果你容易忘记它,你可以使用 ESLint 插件来强制它。
我希望你能在下一个 React 项目中吸取这里学到的所有经验教训。 祝你好运!
资源
- “介绍 Hooks”,React 文档
- “React 中的函数式组件与类组件”,David Joch,Medium
- “ Mixins 被认为是有害的,”Dan Abramov,React 博客
- “React Hooks:最佳实践和心态转变”,Bryan Manuele,Medium
- “React Hooks Snippets For VS Code”,Anthony Davis,Visual Code Marketplace