首页 / React 19 入门教程 / useState 深入

React 19 入门教程

useState 深入

本教程共 50 篇 · 第 19 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

ReactuseState函数式更新惰性初始化状态更新

19. useState 深入

本节目标:掌握 useState 的高级用法,包括函数式更新、惰性初始化,以及对象/数组 state 的正确更新模式。

函数式更新

新状态依赖旧状态时,传函数给 setState:

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

  function increment() {
    setCount(prev => prev + 1);
  }

  function incrementThree() {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    // count 从 0 变成 3
  }

  return <button onClick={incrementThree}>{count}</button>;
}

prev 参数是 React 保证的最新状态值,不是闭包捕获的旧值。

闭包陷阱

在异步回调里用 count 可能是过期的。函数式更新永远拿到最新值。

惰性初始化

初始值需要计算时,传函数而不是直接计算:

// 不好:每次渲染都执行 expensiveCalc()
const [data, setData] = useState(expensiveCalc());

// 好:只在首次渲染执行
const [data, setData] = useState(() => expensiveCalc());
function TodoList() {
  // 从 localStorage 读取,只在首次渲染执行
  const [todos, setTodos] = useState(() => {
    const saved = localStorage.getItem('todos');
    return saved ? JSON.parse(saved) : [];
  });
}

什么时候用惰性初始化

初始值涉及 JSON 解析、localStorage 读取、大数组生成等开销操作时,用函数形式。

对象 state 的更新

展开运算符创建新对象,不要直接修改:

function Form() {
  const [user, setUser] = useState({ name: '', age: 0 });

  function handleNameChange(e) {
    setUser({
      ...user,          // 展开旧属性
      name: e.target.value // 覆盖要改的
    });
  }

  function handleAgeChange(e) {
    setUser({
      ...user,
      age: Number(e.target.value)
    });
  }
}

嵌套对象的更新

嵌套对象要逐层展开:

function Form() {
  const [form, setForm] = useState({
    name: '',
    address: {
      city: '',
      street: ''
    }
  });

  function handleCityChange(e) {
    setForm({
      ...form,
      address: {
        ...form.address,
        city: e.target.value
      }
    });
  }
}

嵌套越深越麻烦,后面会讲 Immer 简化这个操作。

数组 state 的更新

function List() {
  const [items, setItems] = useState([]);

  // 添加:展开旧数组 + 新元素
  function addItem(item) {
    setItems([...items, item]);
  }

  // 删除:filter
  function removeItem(id) {
    setItems(items.filter(item => item.id !== id));
  }

  // 更新某一项:map
  function updateItem(id, newProps) {
    setItems(items.map(item =>
      item.id === id ? { ...item, ...newProps } : item
    ));
  }

  // 插入到指定位置
  function insertAt(index, item) {
    setItems([
      ...items.slice(0, index),
      item,
      ...items.slice(index)
    ]);
  }
}

数组操作速查

  • 添加:[...arr, newItem][newItem, ...arr]
  • 删除:arr.filter(...)
  • 更新:arr.map(...)
  • 插入:[...arr.slice(0, i), item, ...arr.slice(i)]

函数作为 state

state 可以存函数,但不推荐。存数据,在渲染时计算:

// 不好:存函数
const [fn, setFn] = useState(() => () => console.log('hi'));

// 好:存数据,渲染时计算
const [mode, setMode] = useState('edit');
const isEdit = mode === 'edit';

常见错误

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

  function badIncrement() {
    count = count + 1;       // 错误:直接修改
    setCount(count + count); // 错误:基于闭包旧值
  }

  function goodIncrement() {
    setCount(prev => prev + 1); // 正确
  }
}

本节要点

  • 新状态依赖旧状态用函数式更新
  • 初始值开销大用惰性初始化 () => calc()
  • 对象/数组更新用展开运算符创建新引用
  • 不要直接修改 state