React中hooks的调用为啥不能写在条件语句中

468 阅读2分钟

在 React 中,Hooks 是一套用于在函数组件中管理状态和副作用的 API。React Hooks 设计有一些使用规则,其中一条重要规则是不要在条件语句中调用 Hooks。这一规则主要是为了确保 Hooks 的调用顺序在每次渲染时保持一致,这对于 React 内部依赖于 Hooks 调用顺序的机制至关重要。

React Hooks 调用规则

  1. 只在最顶层使用 Hooks:不要在循环、条件语句或嵌套函数中调用 Hooks。
  2. 只在 React 函数组件或自定义 Hooks 中调用 Hooks:不要在普通的 JavaScript 函数中调用 Hooks。

为什么不能在条件语句中调用 Hooks?

React 依赖于 Hooks 的调用顺序来正确地管理和维护状态。每次组件渲染时,React 必须确保每个 Hook 都以相同的顺序被调用。如果你在条件语句中调用 Hooks,可能会导致 Hooks 的调用顺序不一致,从而导致 React 无法正确地管理状态。

具体实现原理

React 通过一个内部的 Hook 链表来跟踪每个组件的 Hooks 调用。每次组件渲染时,React 会遍历这个链表,并根据调用顺序来执行相应的操作。以下是一个简化的示例,展示了 React 如何管理 Hooks 调用顺序:

let currentHookIndex = 0;
const hooks = [];

function useState(initialValue) {
  const hook = hooks[currentHookIndex] || { state: initialValue, queue: [] };
  hooks[currentHookIndex] = hook;
  currentHookIndex++;

  hook.queue.forEach(action => {
    hook.state = action(hook.state);
  });
  hook.queue = [];

  const setState = (action) => {
    hook.queue.push(action);
  };

  return [hook.state, setState];
}

function useEffect(effect, deps) {
  const hook = hooks[currentHookIndex] || { deps: undefined };
  const hasChanged = deps ? deps.some((dep, i) => !Object.is(dep, hook.deps[i])) : true;
  if (hasChanged) {
    effect();
  }
  hook.deps = deps;
  hooks[currentHookIndex] = hook;
  currentHookIndex++;
}


function MyComponent() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log('Effect ran');
  }, [count]);

  if (count > 5) {
    const [extra, setExtra] = useState(0); // 会破坏调用顺序
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

在这个示例中,如果 useStateuseEffect 在条件语句中调用,currentHookIndex 的值将会在不同的渲染中发生变化,从而导致 Hooks 的调用顺序不一致。这会导致 React 无法正确地匹配每个 Hook 的状态,从而引发错误。

正确的使用方式

为了避免上述问题,应该始终在组件的顶层调用 Hooks:

function MyComponent() {
  const [count, setCount] = useState(0);
  const [extra, setExtra] = useState(0);

  useEffect(() => {
    console.log('Effect run');
  }, [count]);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {count > 5 && <p>Extra: {extra}</p>}
    </div>
  );
}

React Hooks 的调用顺序必须在每次渲染时保持一致,以确保 React 能够正确地管理状态和副作用。通过遵循 Hooks 的使用规则,可以避免在条件语句中调用 Hooks,从而确保 Hooks 的调用顺序一致,避免潜在的错误。