Hooks 规则
Hooks 是一项新功能提案,可让您在不编写类的情况下使用 state(状态) 和其他 React 功能。它们目前处于 React v16.7.0-alpha 中,并在 一个开放RFC 中进行讨论。
Hooks 是 JavaScript 函数,但在使用它们时需要遵循两个规则。 我们提供了一个 linter 插件 来自动执行这些规则:
只在顶层调用Hook
**不要在循环,条件或嵌套函数中调用 Hook **。 相反,总是在 React 函数的顶层使用 Hooks。 通过遵循此规则,您可以确保每次组件渲染时都以相同的顺序调用 Hook 。 这就是允许 React 在多个 useState
和 useEffect
调用之间能正确保留 Hook 状态的原因。 (如果你很好奇,我们将在 下面 深入解释。)
只在 React Functions 调用 Hooks
不要在常规 JavaScript 函数中调用 Hook 。 相反,你可以:
- ✅ 在 React 函数式组件中调用 Hooks 。
- ✅ 从自定义 Hooks 调用 Hooks (我们将在下一页中学习 自定义 Hooks )。
通过遵循此规则,您可以确保组件中的所有 stateful (有状态)逻辑在其源代码中清晰可见。
ESLint 插件
我们发布了一个名为 eslint-plugin-react-hooks
的ESLint插件,它强制执行这两个规则。如果您想尝试,可以将此插件添加到项目中:
npm install eslint-plugin-react-hooks@next
// Your ESLint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error"
}
}
将来,我们打算默认将此插件包含在 Create React App 和类似的工具箱中。
**您可以跳到下一页,解释如何编写 自己的 Hooks **(愚人码头注:自定义 Hooks)。 在此页面上,我们将继续解释这些规则背后的原因。
解释说明
如前所述,我们可以在单个组件中使用多个 State 或 Effect Hook:
function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
那么 React 如何知道哪个 state(状态) 对应于哪个 useState
调用呢?答案是 React 依赖于调用 Hooks 的顺序。我们的示例之所以有效,是因为每次渲染时 Hook 调用的顺序都是相同的:
// ------------
// 第一次渲染
// ------------
useState('Mary') // 1. 用'Mary'初始化名称状态变量
useEffect(persistForm) // 2. 添加一个 effect 用于持久化form
useState('Poppins') // 3. 使用 'Poppins' 初始化 surname 状态变量
useEffect(updateTitle) // 4. 添加一个 effect 用于更新 title
// -------------
// 第二次渲染
// -------------
useState('Mary') // 1. 读取 name 状态变量(忽略参数)
useEffect(persistForm) // 2. 替换 effect 以持久化 form
useState('Poppins') // 3. 读取 surname 状态变量(忽略参数)
useEffect(updateTitle) // 4. 替换 effect 用于更新 title
// ...
只要 Hook 调用的顺序在每次渲染之间是相同的,React 就可以将一些本地 state(状态) 与每次渲染相关联。但是如果我们在条件中放置 Hook 调用(例如,persistForm
effect)会发生什么呢?
// 🔴 我们在条件语句中使用Hook,打破了第一条规则
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
name !== ''
条件在第一次渲染时为 true
,因此我们运行此 Hook 。 但是,在下一次渲染时,用户可能会清除form,使条件置为 false
。 现在我们在渲染过程中跳过此 Hook ,Hook 调用的顺序变得不同:
useState('Mary') // 1. 读取 name 状态变量(忽略参数)
// useEffect(persistForm) // 🔴 这个Hook被跳过了
useState('Poppins') // 🔴 2 (但是之前是 3). 读取 surname 状态变量失败
useEffect(updateTitle) // 🔴 3 (但是之前是 4). 替换 effect 失败
React 不知道第二次 useState
Hook 调用返回什么。React 期望这个组件中的第二个 Hook 调用对应于 persistForm
effect,就像之前的渲染一样,但现在已经不存在了。从那时起,在我们跳过的那个 Hook 调用之后的每一个 Hook 调用也会移动一个,从而导致 bug。
这就是为什么我们需要在组件顶层调用 Hook 的原因。 如果我们想要有条件地运行一个效果,我们可以把这个条件 放置 在我们的 Hook 中:
useEffect(function persistForm() {
// 👍 我们不再违反第一条规则了
if (name !== '') {
localStorage.setItem('formData', name);
}
});
请注意,如果使用 我们提供的lint规则 的话,就不需要担心这个问题。但是现在您也知道了 为什么 Hooks 以这种方式工作,以及规则阻止了哪些问题。
下一步
最后,我们已经准备好了解如何 编写自己的Hooks ! 自定义 Hooks 允许您将 React 提供的 Hook 组合到您自己的抽象中,并重用不同组件之间的常见 stateful(有状态) 逻辑。