跳转到主要内容

State 更新

更新 State 看起来很简单,但新手最常见的 bug 也在这里:一次点击加不动、对象更新不生效、数组操作后 UI 没变化…… 这一章把“正确更新 State”讲清楚。

函数式更新:解决“快照”问题

State:组件的记忆 里你已经见过:同一渲染里读到的是快照。 这段代码能正确加 3:

TypeScript
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount((c) => c + 1);
    setCount((c) => c + 1);
    setCount((c) => c + 1);
  }

  return <button onClick={handleClick}>{count}</button>;
}
什么时候必须用函数式更新?
  • 一段逻辑里连续更新同一个 state 多次
  • 更新依赖旧值:加一、追加、切换、累加、基于上一帧计算

更新对象:不要直接修改

下面是最容易写出来、也最容易出问题的写法:

TypeScript
const [person, setPerson] = useState({ name: 'Ada', age: 20 });

function handleBirthday() {
  person.age = person.age + 1;
  setPerson(person);
}
为什么这是个坑
  • 你把 person 改成了“同一个对象”,React 很难判断“到底变没变”。
  • 更糟的是:mutation 会让调试和回溯变得困难。

正确做法是创建一个新对象:

TypeScript
const [person, setPerson] = useState({ name: 'Ada', age: 20 });

function handleBirthday() {
  setPerson({ ...person, age: person.age + 1 });
}

更新数组:新增、删除、替换

TypeScript
const [items, setItems] = useState<string[]>(['A', 'B']);

function addItem() {
  setItems([...items, 'C']);
}

function removeFirst() {
  setItems(items.slice(1));
}

function updateFirst() {
  setItems(items.map((x, i) => (i === 0 ? x + '!' : x)));
}
小抄:常用不可变操作
  • 新增:[...items, newItem]
  • 删除:items.filter(...)
  • 替换:items.map(...)
  • 切片:items.slice(...)

下一步

  • 当 state 逻辑变复杂(多字段、多分支),考虑用 useReducer
  • 当你需要在不触发渲染的情况下保存某个值,考虑 Ref

动手练习

把“普通更新”改成函数式更新,让一次点击能 +3。

🌐浏览器运行
State 更新 - React 文档 - React 文档