不要再循环语句、条件语句或者嵌套函数中声明hooks。相反,在任何返回之前,总是在React函数的顶层声明hook。通过遵循此规则,您可以确保在每次呈现组件时以相同的顺序调用hook。这使得React能够在多个useState和useEffect调用之间正确保存hook的状态。(如果你感到好奇,我们将在下面深入解释。)
如之前所述,我们可以在单一组件中声明多个state或者Effect
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依赖于hook的调用顺序。我们的例子之所以有效,是因为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就可以将一些局部状态与它们关联起来。但是,如果我们在条件中放入一个Hook调用(例如,persistForm Effect),会发生什么呢?
// We're breaking the first rule by using a Hook in a condition
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
在第一次渲染时,name !== ’ ’ 条件为真,所以我们运行这个Hook。但是,在下一次呈现时,用户可能会clear the form,使条件为假。现在我们在渲染过程中跳过了这个Hook, 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 Effect,就像之前的渲染一样,但现在不是这样了。从那时起,在我们跳过的那个Hook调用之后的每一个Hook调用也会转移一个,从而导致bugs。
这就是为什么必须在组件的顶层调用hook的原因。如果我们想要有条件地运行一个Effect,我们可以把这个条件放到hook中:
useEffect(function persistForm() {
// We're not breaking the first rule anymore
if (name !== '') {
localStorage.setItem('formData', name);
}
});
注意,如果使用提供的lint规则,则不需要担心这个问题。但是现在您也知道了hook为什么会以这种方式工作,以及规则阻止了哪些问题。
原文章