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

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

为了确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。

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

javascript 复制代码
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 调用的顺序。

javascript 复制代码
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];
}

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

javascript 复制代码
// ------------
// 首次渲染
// ------------
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
 
 
// ...

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

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

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

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

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

比如:

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

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

javascript 复制代码
useState('Mary')           // 1. 读取变量名为 name 的 state(参数被忽略)
// useEffect(persistForm)  // ???? 此 Hook 被忽略!
useState('Poppins')        // ???? 2(之前为 3)。读取变量名为 surname 的 state 失败
useEffect(updateTitle)     // ???? 3 (之前为 4)。替换更新标题的 effect 失败
  1. 第一次调用 useState('Mary'),此时 hookIndex=0,状态被存储在 hookStates[0]

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

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

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

相关推荐
速盾cdn28 分钟前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学129 分钟前
CSS浮动
前端·css·css3
什么鬼昵称30 分钟前
Pikachu-csrf-CSRF(POST)
前端·csrf
golitter.1 小时前
Vue组件库Element-ui
前端·vue.js·ui
儒雅的烤地瓜1 小时前
JS | JS中判断数组的6种方法,你知道几个?
javascript·instanceof·判断数组·数组方法·isarray·isprototypeof
道爷我悟了1 小时前
Vue入门-指令学习-v-on
javascript·vue.js·学习
27669582921 小时前
京东e卡滑块 分析
java·javascript·python·node.js·go·滑块·京东
golitter.1 小时前
Ajax和axios简单用法
前端·ajax·okhttp
PleaSure乐事1 小时前
【Node.js】内置模块FileSystem的保姆级入门讲解
javascript·node.js·es6·filesystem
雷特IT2 小时前
Uncaught TypeError: 0 is not a function的解决方法
前端·javascript