• 为什么避免在循环、条件或嵌套函数中调用 Hooks


    为什么避免在循环、条件或嵌套函数中调用 Hooks

    为了确保 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;
      });
     
     
      // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

    let hookStates = []; // 放着此组件的所有的hooks数据
    let hookIndex = 0; // 代表当前的hooks的索引
    function useState(initialState){
      // 如果有老值取老值,没有取默认值
      hookStates[hookIndex] = hookStates[hookIndex] || initialState;
      // 暂存索引
      let currentIndex = hookIndex;
      //setState方法,将当前值置为新值
      function setState(newState){
        hookStates[currentIndex] = newState;
      //置完新值,立刻触发渲染
        render();
      }
      return [hookStates[hookIndex++], setState];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    // ------------
    // 首次渲染
    // ------------
    useState('Mary')           // 1. 使用 'Mary' 初始化变量名为 name 的 state
    useEffect(persistForm)     // 2. 添加 effect 以保存 form 操作
    useState('Poppins')        // 3. 使用 'Poppins' 初始化变量名为 surname 的 state
    useEffect(updateTitle)     // 4. 添加 effect 以更新标题
     
     
    // -------------
    // 二次渲染 
    // useState - 不再使用传入的默认值,而是返回上次渲染时存储的状态值。
    // useEffect 首先运行之前 useEffect 的清理函数(如果有的话),然后再运行新的副作用函数。
    // -------------
    useState('Mary')           // 1. 读取变量名为 name 的 state(Mary参数被忽略)
    useEffect(persistForm)     // 2. 清除并替换上一次 form 的 effect
    useState('Poppins')        // 3. 读取变量名为 surname 的 state(Poppins参数被忽略)
    useEffect(updateTitle)     // 4. 清除并替换上一次更新标题的 effect
     
     
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    useEffect Hook有什么作用?如何理解 替换与清除?

    告诉 React 需要在完成DOM更新之后(渲染之后)执行一些“副作用”(如数据获取、手动更改DOM、设置订阅或者清除上一次的副作用等)。

    useEffect(persistForm) 在首次渲染时添加了一个副作用,即 persistForm 函数。然后,在随后的组件更新(第二次渲染)中,相同的 useEffect(persistForm) 调用会首先清除前一次的副作用(如果 persistForm 返回了一个清除函数的话),然后再执行新的 persistForm 副作用。

    这样确保了副作用是最新的,并且在多次渲染之间不会有冲突。这也是为什么我们说第二次和随后的渲染是“替换”前一次的副作用。

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

    比如:

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

    在第一次渲染中 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 失败
    
    • 1
    • 2
    • 3
    • 4
    1. 第一次调用 useState('Mary'),此时 hookIndex=0,状态被存储在 hookStates[0]

    2. 然后我们跳过 useEffect(persistForm)

    3. 接下来的 useState('Poppins') 会使用 hookIndex=1,但在上一次渲染中,它使用了 hookIndex=2。这会导致 surname 的状态从错误的位置获取。

    4. 以此类推,后续的 useEffect(updateTitle) 也会使用错误的索引。

  • 相关阅读:
    k8s--基础--23.2--认证-授权-准入控制--认证
    iNFTnews | 为元宇宙做准备:eBay探索人工智能在电子商务中的应用
    8-8归并排序
    Flutter 常见错误记录总结
    Spring框架系列(9) - Spring AOP实现原理详解之AOP切面的实现
    5种基本类型之外的数据类型是object——对象、添加、读取
    数字化转型企业成功的关键,用数据创造价值
    Linux/Ubuntu/Arm设备中通过/proc/stat等文件计算Cpu使用率
    英语词汇篇 - 常见词根词缀
    自动驾驶——基础架构
  • 原文地址:https://blog.csdn.net/weixin_43850639/article/details/133902796