React useState 解读

🧠 01|useState 本质:React 给你分配"状态槽位"

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

核心关键点如下👇

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

组件里写的:

scss 复制代码
const [count, setCount] = useState(0);

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

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

可以想象成这样:

scss 复制代码
Fiber
 └─ memoizedState
       ├─ HookNode1  (对应第一个 useState)
       ├─ HookNode2  (对应第二个 useState)
       └─ ...

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

3. setState 只是把更新"入队"

每个 HookNode 都有个 update queue:

arduino 复制代码
queue: [update1, update2, ...]

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

4. 不能在条件中使用hook

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

第一次:

css 复制代码
useState A → 节点1
useState B → 节点2

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

css 复制代码
useState A → 节点1

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

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

🔍 02|useState 的参数与返回值(TypeScript 思维)

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

① 直接值

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

② 懒初始化函数(lazy initializer)

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

🧩 03|state 的特点

自动批处理

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

替换非合并

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

对象必须手动展开:

ini 复制代码
setForm(prev => ({ ...prev, name: 'xx' }));

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

ini 复制代码
setCount(c => c + 1);

📦 04|mini-hooks

ini 复制代码
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();
相关推荐
海参崴-15 分钟前
C++代码格式规范
java·前端·c++
谢尔登33 分钟前
【React】setState 触发渲染的流程
前端·react.js·前端框架
摸鱼仙人~1 小时前
Vue中markdown-it基础使用教程
前端·javascript·vue.js
落魄江湖行1 小时前
入门篇二:Nuxt 4路由自动生成:告别手动配置路由的日子
前端·vue.js·typescript·nuxt4
CQU_JIAKE2 小时前
4.4【Q】
java·前端·javascript
小陈工2 小时前
Python Web开发入门(十二):使用Flask-RESTful构建API——让后端开发更优雅
开发语言·前端·python·安全·oracle·flask·restful
木斯佳2 小时前
前端八股文面经大全:字节前端一面(2026-04-03)·面经深度解析
前端·面试题·面经
xiaotao1312 小时前
第八章:实战项目案例
前端·vue.js·vite·前端打包
We་ct2 小时前
JS手撕:性能优化、渲染技巧与定时器实现
开发语言·前端·javascript·面试·性能优化·定时器·性能
taWSw5OjU3 小时前
vue对接海康摄像头-H5player
开发语言·前端·javascript