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();
相关推荐
qq_406176149 分钟前
JavaScript闭包:从底层原理到实战
开发语言·前端·javascript
冰暮流星9 分钟前
javascript之Math对象——绝对值,开次方,四舍五入
前端·javascript
啊啊啊啊懒11 分钟前
vite创建完项目之后vue文件中有标签报错
前端·javascript·vue.js
Knight_AL11 分钟前
从 bootstrap.yml 到 Config Data 的一次架构升级
前端·架构·bootstrap
共享家952720 分钟前
测试常用函数(下)
java·服务器·前端
问道飞鱼28 分钟前
【Rust开发知识】Actix-web 开发环境搭建完整教程
开发语言·前端·rust·actix-web
a1760293175728 分钟前
任天堂超级马里奥合集系列游戏130合1 解压即玩 天马G前端整合包附使用教程
前端·游戏·玩游戏·游戏机·单机游戏
雪忆·HL35 分钟前
CSS3知道这些就足够了
前端·css·css3
心机boy22935 分钟前
CSS3网格布局、过渡及2D效果
前端·javascript·css3
空白36 分钟前
自学HTML5+CSS3丨第三天丨详解SEO
前端·css3·html5