不要在循环,条件或嵌套函数中调用 Hook

不要在循环,条件或嵌套函数中调用 Hook_第1张图片

只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。(如果你对此感到好奇,我们在下面会有更深入的解释。)

我们可以在单个组件中使用多个 State Hook 或 Effect Hook

function Form() {
  // 1. 使用变量名为 name 的 state
  const [name, setName] = useState('Mary');


  // 2. 使用 effect 以保存 form 操作
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });


  // 3. 使用变量名为 surname 的 state
  const [surname, setSurname] = useState('Poppins');


  // 4. 使用 effect 以更新标题
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });


  // ...
}

那么 React 怎么知道哪个 state 对应哪个 useState?答案是 React 靠的是 Hook 调用的顺序。

let hookStates = []; // 放着此组件的所有的hooks数据
let hookIndex = 0; // 代表当前的hooks的索引
function useState(initialState){
  // 如果有老值取老值,没有取默认值
  hookStates[hookIndex] = hookStates[hookIndex] || initialState;
  // 暂存索引
  let currentIndex = hookIndex;
  function setState(newState){
    hookStates[currentIndex] = newState;
    render();
  }
  return [hookStates[hookIndex++], setState];
}

因为我们的示例中,Hook 的调用顺序在每次渲染中都是相同的,所以它能够正常工作:

// ------------
// 首次渲染
// ------------
useState('Mary')           // 1. 使用 'Mary' 初始化变量名为 name 的 state
useEffect(persistForm)     // 2. 添加 effect 以保存 form 操作
useState('Poppins')        // 3. 使用 'Poppins' 初始化变量名为 surname 的 state
useEffect(updateTitle)     // 4. 添加 effect 以更新标题


// -------------
// 二次渲染
// -------------
useState('Mary')           // 1. 读取变量名为 name 的 state(参数被忽略)
useEffect(persistForm)     // 2. 替换保存 form 的 effect
useState('Poppins')        // 3. 读取变量名为 surname 的 state(参数被忽略)
useEffect(updateTitle)     // 4. 替换更新标题的 effect


// ...

只要 Hook 的调用顺序在多次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联。但如果我们将一个 Hook (例如 persistForm effect) 调用放到一个条件语句中会发生什么呢?

// ???? 在条件语句中使用 Hook 违反第一条规则
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

在第一次渲染中 name !== '' 这个条件值为 true,所以我们会执行这个 Hook。但是下一次渲染时我们可能清空了 name,表达式值变为 false。此时的渲染会跳过该 Hook,Hook 的调用顺序发生了改变:

useState('Mary')           // 1. 读取变量名为 name 的 state(参数被忽略)
// useEffect(persistForm)  // ???? 此 Hook 被忽略!
useState('Poppins')        // ???? 2 (之前为 3)。读取变量名为 surname 的 state 失败
useEffect(updateTitle)     // ???? 3 (之前为 4)。替换更新标题的 effect 失败

React 不知道第二个 useState 的 Hook 应该返回什么。React 会以为在该组件中第二个 Hook 的调用像上次的渲染一样,对应的是 persistForm 的 effect,但并非如此。从这里开始,后面的 Hook 调用都被提前执行,导致 bug 的产生。

这就是为什么 Hook 需要在我们组件的最顶层调用。如果我们想要有条件地执行一个 effect,可以将判断放到 Hook 的内部:

useEffect(function persistForm() {
    // ????将条件判断放置在 effect 中
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

不过你现在知道了为什么 Hook 会这样工作,也知道了这个规则是为了避免什么问题。

hooks 实现原理

import React from "react";
import ReactDOM from "react-dom";


// ["Mary", undefined, "Poppins", undefined]
let hookStates = [];
let hookIndex = 0;
function useState(initialState){
  hookStates[hookIndex]=hookStates[hookIndex] || initialState;
  let currentIndex = hookIndex;
  function setState(newState){
    hookStates[currentIndex] = newState;
    render();
  }
  return [hookStates[hookIndex++], setState];
} 


function useEffect(callback,dependencies){
  if(hookStates[hookIndex]){
      let lastDeps = hookStates[hookIndex];
      let same = dependencies.every((item,index)=>item === lastDeps[index]);
      if(same){
        hookIndex++;
      }else{
        hookStates[hookIndex++] = dependencies;
         setTimeout(callback);
      }
  }else{
     hookStates[hookIndex++] = dependencies;
     setTimeout(callback);
  }
}


function Counter() {
  const [name, setName] = useState("Mary");


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


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


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

{name}:{surname}

); } function render() { hookIndex = 0; ReactDOM.render(, document.getElementById("root")); } render();

你可能感兴趣的:(js,javascript,java,python,react)