React 指南

2 阅读2分钟

useState

1. 本质

useState 会在当前组件的 Fiber 上分配一个 Hook 节点,用来存储状态和更新队列。每次渲染中,React 按固定顺序读取这些节点,因此你总能拿到正确的最新 state。

核心关键点如下👇

1. 函数组件每次渲染都是“重新执行”

组件里写的:

const [count, setCount] = useState(0);

每次渲染都会执行一次。但 state 不存放在函数里,而存在 Fiber 结构里。

2. 每个组件 Fiber 都维护一个 hooks 链表

可以想象成这样:

Fiber
 └─ memoizedState
       ├─ HookNode1  (对应第一个 useState)
       ├─ HookNode2  (对应第二个 useState)
       └─ ...

React 每次调用 Hook 时,会沿着链表走一个节点。

3. setState 只是把更新“入队”

每个 HookNode 都有个 update queue:

queue: [update1, update2, ...]

下一次渲染前,React 会把这些更新依次应用,然后再返回最新值。

4. 不能在条件中使用hook

因为每次渲染 React 都是按顺序“走链表”:

第一次:

useState A → 节点1
useState B → 节点2

第二次如果你写了条件分支导致 B 不执行:

useState A → 节点1

React 就会把节点2 的状态错读为节点1,状态直接乱套。

Hook 顺序必须固定才能保证取到正确的 state。

2. 参数与返回值

function useState<S>(
  initialState: S | (() => S)
): [S, (update: S | ((prev: S) => S)) => void]
  • initialState 支持懒初始化函数(只在首次执行)
  • setState 支持两种更新方式:直接值、更新函数

① 直接值

const [count, setCount] = useState(0);
  • 类型是你传入的值类型,比如 0 → number,"abc" → string。
  • 每次组件重新渲染时,这个值不会再被计算一次。
  • 如果初始值计算开销大,每次渲染直接写计算式可能性能不佳。

② 懒初始化函数(lazy initializer)

const [data, setData] = useState(() => expensiveComputation());
  • 函数只在第一次渲染调用,返回值作为初始 state。
  • 好处:延迟计算开销,避免每次渲染都执行复杂逻辑。

3. 特点

自动批处理

React 会自动批处理(batch),多个 setState → 只渲染一次(视版本和环境而定)

替换非合并

useState 是“替换”,不像 class setState 那样“合并”

对象必须手动展开:

setForm(prev => ({ ...prev, name: 'xx' }));

函数式更新可以避免读到旧值

setCount(c => c + 1);

4. mini-hooks

let currentFiber = null;

// 渲染入口
function render(component) {
  currentFiber = component.fiber;
  currentFiber.hookIndex = 0;

  if (!currentFiber.hooks) {
    currentFiber.hooks = [];
    currentFiber.isMount = true;
  } else {
    currentFiber.isMount = false;
  }

  const result = component();
  console.log("Rendered:", result);
  return result;
}

// 创建组件(带 fiber)
function createComponent(fn) {
  fn.fiber = { hooks: null, hookIndex: 0 };
  return fn;
}

function useState(initialState) {
  const fiber = currentFiber;
  const hookIndex = fiber.hookIndex++;

  if (fiber.isMount) {
    const hook = {
      memoizedState:
        typeof initialState === "function" ? initialState() : initialState,
      queue: [],
    };
    fiber.hooks.push(hook);
  }

  const hook = fiber.hooks[hookIndex];

  hook.queue.forEach((update) => {
    const prev = hook.memoizedState;
    hook.memoizedState =
      typeof update === "function" ? update(prev) : update;
  });

  hook.queue = [];

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

  return [hook.memoizedState, setState];
}

// 调度器
let rerenderScheduled = false;
function rerender() {
  if (!rerenderScheduled) {
    rerenderScheduled = true;
    setTimeout(() => {
      rerenderScheduled = false;
      render(App);
    }, 0);
  }
}

const App = createComponent(function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("hello");

  return {
    count,
    text,
    inc: () => setCount((c) => c + 1),
    changeText: () => setText((t) => t + "!"),
  };
});

// 首次渲染
const app = render(App);

// 调用更新
app.inc();
app.inc();
app.changeText();