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();
相关推荐
辰同学ovo20 分钟前
Pinia极速入门:核心概念与入门指南
前端·javascript·vue.js
余瑜鱼鱼鱼27 分钟前
Thread类中run和start的区别
java·开发语言·前端
计算机程序设计小李同学29 分钟前
基于位置服务的二手图书回收平台
java·前端·vue.js·spring boot·后端
We་ct40 分钟前
LeetCode 14. 最长公共前缀:两种解法+优化思路全解析
前端·算法·leetcode·typescript
遗憾随她而去.44 分钟前
前端检查内存泄露
前端
换日线°1 小时前
微信小程序对接位置服务(腾讯、高德)完成路径规划
前端·微信小程序·vue
广州华水科技1 小时前
如何通过单北斗变形监测系统提升水库安全监测效果?
前端
u1301301 小时前
深入理解 M3U8 与 HLS 协议:从原理到实战解析
前端·音视频开发·流媒体·hls·m3u8
_OP_CHEN1 小时前
【前端开发之CSS】(二)CSS 选择器终极指南:从基础到进阶,精准拿捏页面元素!
前端·css·html·网页开发·css选择器
ヤ鬧鬧o.1 小时前
HTML安全密码备忘录
前端·javascript·css·html·css3