[React Hooks 翻译] 5-8 Hook规则

1,502 阅读3分钟

钩子是JavaScript函数,但在使用它们时需要遵循两个规则。我们提供了一个 linter 插件来自动执行这些规则

只在最顶层调用Hook

不要在循环、条件和嵌套的函数中调用Hook。

遵循此规则可以确保每次组件render时都以相同的顺序调用Hook。这是React能在多个useState和useEffect之间正确保存Hook的state原因。 (下面将深入解释。)

只在React函数和自定义Hook中调用Hook

不要在普通的JS函数中调用Hook

  1. 只在React函数中调用Hook
  2. 只在自定义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组合到您自己的抽象中,并重用不同组件之间的常见有状态逻辑