JavaScript / 前端 / 译文 · 10月 7, 2020 0

React v16.8.x – Hooks 规范

Hooks 是js函数,但是使用时你需要遵循下面两条规则。我们提供了专门的检测插件来强制使用这些规则:

在最顶层调用 Hooks

不要在循环,条件,或者嵌套函数内部调用hooks。
你应该在React函数组件的最顶层调用。这样做,能确保每次组件渲染时都以相同的顺序调用Hook。这是React能在在多个useStateuseEffect调用之间正确保留Hook状态的原因。

只在React函数组件中使用

不要在普通JS函数中使用。

你应该:

  • 在函数组件中调用
  • 在自定义hooks中调用

这样做能确保组件中的所有有状态逻辑清晰可见。

ESLint 插件

我们发布了ESLint 插件eslint-plugin-react-hooks。包含两条规则,如果你想尝试,可以添加到你的项目中去。

npm install eslint-plugin-react-hooks
// ESLint 配置
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error"
  }
}

将来,我们打算默认将此插件包含在Create React App和类似的工具包中。

深入解释规则

我们可以多次使用hooks:

function Form() {

  const [name, setName] = useState('Mary');

  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });

  const [surname, setSurname] = useState('Poppins');

  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });

  // ...
}

那么React如何知道哪个状态对应于哪个useState调用?答案是React依赖于调用Hooks的顺序。我们的示例有效,因为Hook调用的顺序在每个渲染上都是相同的:

// ------------
// 第一次 render
// ------------
useState('Mary')           // 1. 初始化 name  
useEffect(persistForm)     // 2. 添加 effect 保留表单
useState('Poppins')        // 3. 初始化 surname
useEffect(updateTitle)     // 4. 添加 effect 更新 title

// -------------
// 第二次 render
// -------------
useState('Mary')           // 1. 读取 name 的状态值,忽略参数
useEffect(persistForm)     // 2. 替换 effect 以保存表单
useState('Poppins')        // 3. 读取 surname 的状态值,忽略参数
useEffect(updateTitle)     // 4. 替换 effect 以更新 title

// ...

只要Hook调用的顺序在渲染之间是相同的,React就可以将一些本地状态与它们中的每一个相关联。但是如果我们在条件中放置Hook调用(例如,persistForm效果)会发生什么?

// 不按照第一条规则,强行在条件中使用hooks
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

name !== ''条件在第一次渲染时为true,因此我们运行此Hook。但是,在下一次渲染时,用户可能会清除表单,使条件为false。现在,在渲染过程中就会跳过这个Hook,hooks的调用顺序将会改变:

useState('Mary')           // 读取 name 
// useEffect(persistForm)  // 🔴 hooks 被跳过!
useState('Poppins')        // 🔴 无法读取 surname!!
useEffect(updateTitle)     // 🔴 无法执行 effect!!

React将会不知道第二次useState Hook调用返回什么。React期望此组件中的第二个Hook调用对应于persistForm effect,就像在前一个渲染期间一样,但它不再存在。从这一节点开始,在我们跳过的那个之后的每个下一个Hook调用也会移动一位,导致错误。

这就是必须在我们组件的顶层调用Hooks的原因。 如果我们想要有条件地运行一个效果,我们可以把这个条件放在我们的Hook中:

 useEffect(function persistForm() {
    // 👍 完全符合规范
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });!

如果使用提供的lint规则,则无需担心此问题。但是现在你也已知道为什么Hooks以这种方式工作,以及规范避免了哪些问题。

冀ICP备19028007号