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();
相关推荐
jay神3 小时前
基于 YOLOv8 的轴承缺陷检测(Web 版)
前端·人工智能·yolo
jay神3 小时前
基于 YOLOv8 的森林火灾检测系统(Web 版)
前端·yolo
code2cat3 小时前
HTML基础篇之第一张网页:从index.html到浏览器里的任务清单
前端·html
miss4 小时前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan5 小时前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真5 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment5 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan5 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
总要学点什么5 小时前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模
上海懒图科技5 小时前
工厂厂区电子地图定位导航:厂区人车精准定位,安全合规双保障
前端·arcgis·vue