React 使用 Hook 的一些规则
一、使用 Hook 的两个规则
Hook 是 javascript 函数,但是在使用的时候需要遵循两个规则,而 React 提供了一个 linter 插件来自动执行这些规则。
1、最高级使用 Hook
不要在循环、条件或者嵌套函数中调用 Hook。
使用 Hook 的时候,应当总是在 React 函数的顶层使用。遵循这个规则,能够保证每次组件呈现的时候,都能够以相同的顺序调用 Hook。这也是能够保证 React 在多个 useState
和 useEffect
调用之间也能够保留 Hook 的 state 的原因。
2、只在 React Function 中使用 Hooks
不要在常规的 JavaScript 函数中调用 Hook。
可以在下面两个地方调用 Hook:
- 从 React Function 组件调用 Hooks
- 从自定义的 Hook 中调用 Hook(关于自定义 Hook 见:https://reactjs.org/docs/hooks-custom.html)
通过遵循这个规则,你可以确保组件中的所有有状态逻辑从其源代码中清晰可见。
二、ESLint 插件
React 发布了一个 eslint-plugin-react-hooks 插件,强制的执行上面的两个 Hook 规则。
如果想要尝试这个插件:
npm install eslint-plugin-react-hooks@next
Eslint config 内容:
// Your ESLint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error"
}
}
将来,React 会把这个插件包含在 Create React App 之类的工具包中,用以支持 Hook 的使用。
三、规则解释
下面的代码中使用了多个 useState
和 useEffect
:
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 对应于哪个 uesState
调用的?
答案是因为 React 依赖于调用 Hook 的顺序。
上面的示例有效,是因为 Hook 调用的顺序在每个 render 时都是相同的:
// ------------
// 第一次渲染
// ------------
useState('Mary') // 1. 初始化 name = 'mary' 的变量
useEffect(persistForm) // 2. 设置一个 effect 用以保存 form 内容
useState('Poppins') // 3. 使用 `Poppins` 初始化 surname 变量
useEffect(updateTitle) // 4. 设置一个 effect 更新 document.title
// -------------
// 第二次渲染
// -------------
useState('Mary') // 1. 读取 name 变量(忽略参数)
useEffect(persistForm) // 2. 替换之前设置的 effect ,设置新的 effect 用以保存 form 内容
useState('Poppins') // 3. 读取 surname 变量(忽略参数)
useEffect(updateTitle) // 4. 替换之前设置的 effect,设置新的 effect 修改 document.title
只要 Hook 调用的顺序在渲染之间是相同的,React 就可以将一些本地状态与它们中的每一个相关联。但是如果在 if 中放置 Hook 调用(例如, persisForm effect)会发生什么?
// 在 if 中使用了 Hook 打破了第一条规则
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
name !== ''
在第一次渲染时为 true,因此会运行上面的 Hook,但是在下一次渲染时,用户可能会清除表单,此时条件变成 false
。然后会在 render 中跳过这个 Hook,Hook 调用的顺序变得不同:
useState('Mary') // 1. 读取 name 变量(忽略参数)
// useEffect(persistForm) // 这个 Hook 被跳过了
useState('Poppins') // 2 本来顺序应该是 3
useEffect(updateTitle) // 3 本来顺序应该是 4
React 不知道第二次 useStat
Hook调用返回什么。
React 期望此组件中的第二个 Hook 调用对应于 persistForm
effect,就像在前一个 render 一样,但它已经不存在了。
而正因为上述情况,在我们跳过的那个 Hook 之后的每个下一个Hook调用也会移动一个位置,导致错误。
这就是必须在我们组件的顶层调用Hooks的原因。如果我们想要有条件地运行一个效果,我们可以把这个条件放在我们的Hook中:
useEffect(function persistForm() {
// 这里没有打破置顶规则
if (name !== '') {
localStorage.setItem('formData', name);
}
});
如果使用提供的lint规则,则无需担心此问题
文章版权:Postbird-There I am , in the world more exciting!
本文链接:http://ptbird.cn/react-hook-rules.html
转载请注明文章原始出处 !