钩子是JavaScript函数,但在使用它们时需要遵循两个规则。我们提供了一个 linter 插件来自动执行这些规则
只在最顶层调用Hook
不要在循环、条件和嵌套的函数中调用Hook。
遵循此规则可以确保每次组件render时都以相同的顺序调用Hook。这是React能在多个useState和useEffect之间正确保存Hook的state原因。 (下面将深入解释。)
只在React函数和自定义Hook中调用Hook
不要在普通的JS函数中调用Hook
- 只在React函数中调用Hook
- 只在自定义Hook中调用Hook
ESLinit 插件
使用 eslint-plugin-react-hooks 确保上面2条规则
npm install eslint-plugin-react-hooks --save-dev
// 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和类似的工具包中。
解释
如前所述,我们可以在单个组件中使用多个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如何知道哪个状态对应于哪个useState?
答案是React依赖于调用Hooks的顺序。Hook调用的顺序在每次渲染时都是相同的
// ------------
// First render
// ------------
useState('Mary') // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm) // 2. Add an effect for persisting the form
useState('Poppins') // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle) // 4. Add an effect for updating the title
// -------------
// Second render
// -------------
useState('Mary') // 1. Read the name state variable (argument is ignored)
useEffect(persistForm) // 2. Replace the effect for persisting the form
useState('Poppins') // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle) // 4. Replace the effect for updating the title
// ...
只要Hook调用的顺序在渲染之间是相同的,React就可以将一些local state与Hook一一对应。但是如果我们把Hook放在条件语句中(例如,persistForm效果)会发生什么呢?
// 🔴 We're breaking the first rule by using a Hook in a condition
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
第一次渲染时name !== ' '可能 是true,下一次渲染时可能用户更改了表单,导致name !== ' '是false,那么这次render将会跳过Hook的执行
useState('Mary') // 1. Read the name state variable (argument is ignored)
// useEffect(persistForm) // 🔴 This Hook was skipped!
useState('Poppins') // 🔴 2 (but was 3). Fail to read the surname state variable
useEffect(updateTitle) // 🔴 3 (but was 4). Fail to replace the effect
React将不知道第二次useState Hook调用返回什么。 React期望第二个Hook调用对应于persistForm效果,就像在前一个render时一样,但useEffect(persistForm)不存在。此后后面的一个Hook都和前一次对不上号了,然后引发错误。
**这就是必须在我们组件的顶层调用Hooks的原因。**如果我们想要有条件地执行一个effect,我们应该把这个条件放在Hook之中:
useEffect(function persistForm() {
// 👍 We're not breaking the first rule anymore
if (name !== '') {
localStorage.setItem('formData', name);
}
});
如果使用提供的lint规则,则无需担心此问题。但是现在你也知道为什么Hooks以这种方式工作
下一篇
我们已经准备好了解如何编写自己的Hooks! Custom Hooks允许您将React提供的Hook组合到您自己的抽象中,并重用不同组件之间的常见有状态逻辑