useState 的 9个常见坑与最佳实践

1. 异步更新导致取不到旧值

  • 坑:useState的更新是异步批量处理的,如果直接使用旧值计算新值,可能会出错。
jsx 复制代码
const [count,setCount] = useState(0)

function handleClick(){
  setCount(count + 1);
  setCount(count + 1); // ❌ 结果不是 +2,而是 +1
}
最佳实践:用函数式更新,让React保证基于最新状态计算
jsx 复制代码
setCount(prev=>prev+1)

2.初始值计算浪费性能

  • 坑:如果初始值需要复杂的计算,每次组件渲染都会执行:
jsx 复制代码
const [value,setValue] = useState(expensiveComputation()); // ❌ 每次 render 都计算
最佳实践:用懒初始化(传函数)
jsx 复制代码
const [value,setValue] = useState(()=>expensiveComputation()) // z只会执行一次

3.直接修改状态对象或数组,React可能不会触发重新渲染

  • 坑:useState 不会做深拷贝,如果直接修改对象的属性/数组,可能不会触发渲染
js 复制代码
const [user, setUser] = useState({ name: 'Tom' });
user.name = 'Jerry'; // ❌ 不会触发更新
最佳实践:setUser(prev => ({ ...prev, name: 'Jerry' }));

4.状态更新后立即读取

  • 坑:setState 不会立即更新state的值,它要等下一次渲染才生效
jsx 复制代码
setCount(1)
console.log(count) // 还是旧值
最佳实践:把setState的逻辑加到useEffect中
jsx 复制代码
useEffect(() => {
  console.log(count); // ✅ 已更新
}, [count]);

5.频繁更新导致多次渲染

  • 坑:连续调用多次setState(不是函数式)的话会触发多次渲染
jsx 复制代码
setCount(1);
setName('Tom'); // ❌ 两次渲染
最佳实践:
  • 同一事件中多次更新,react18会自动批处理
  • 如果跨组件调用,可用 unstable_batchedUpdates 手动批处理
jsx 复制代码
import React, { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("hello");

  const handleClick = () => {
    setTimeout(() => {
      setCount(c => c + 1);
      setText("world");
      // React 在 setTimeout 中不会自动批处理
      // 这里会渲染两次
    }, 0);
  };

  console.log("render"); // 每次渲染都会打印,,渲染两次

  return (
    <div>
      <p>{count} - {text}</p>
      <button onClick={handleClick}>更新</button>
    </div>
  );
}


// unstable_batchUpdates 手动批处理
import React,{useState} from 'react';
import {unstable_batchedUpdates} from 'react-dom';

export default function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("hello");
  
  const handleClick = ()=>{
      setTimeout(()=>{
          unstable_batchUpdates(()=>{
            setCount(c => c + 1);
            setText("world");
            // 这里会被批处理,只渲染一次
          })
      },0)
  }

  console.log("render"); // 每次渲染都会打印,渲染一次

  return (
    <div>
      <p>{count} - {text}</p>
      <button onClick={handleClick}>更新</button>
    </div>
  );
}

6.状态初始值依赖props

  • 坑:useState 的初始值只在首次渲染的时候用,后续props变化不会自动更新。
js 复制代码
const [value,setValue] = useState(props.defaultValue) // props变了没同步
最佳实践:如果想要state跟着props变,使用useEffect
js 复制代码
useEffect(() => {
  setValue(props.defaultValue);
}, [props.defaultValue]);

7.把状态放得太深

  • 坑:状态放在子组件时,父组件没法控制。状态放在父组件时,可能导致不必要的渲染
最佳实践:
  • 状态**提升(Lifting State Up)**到需要的最小公共父组件
  • 或者用 useReducer / 全局状态管理(Redux、Zustand 等)

8.滥用复杂对象当 state

  • 坑: 复杂嵌套对象更新麻烦且容易出错:
css 复制代码
setUser({ ...user, address: { ...user.address, city: 'NY' } });

最佳实践:

  • 拆成多个 useState
  • 或者用 useReducer 管理复杂结构 坑:

9.**在循环/条件中调用 useState**

  • 坑:Hook 调用顺序必须固定,否则 React 会报错:
scss 复制代码
if (show) {
  const [count, setCount] = useState(0); // ❌ 条件调用
}

最佳实践:

  • 永远在组件顶层调用 Hook
scss 复制代码
const [count, setCount] = useState(0);
if (show) { /* 在这里用 count */ }
相关推荐
gAlAxy...16 分钟前
深入理解 Cookie 与 Session —— Web 状态保持详解与实战
前端
专注VB编程开发20年22 分钟前
c#,vb.net全局多线程锁,可以在任意模块或类中使用,但尽量用多个锁提高效率
java·前端·数据库·c#·.net
JarvanMo26 分钟前
Google Connect 8月14日纪实
前端
猩猩程序员1 小时前
Go 1.24 全面拥抱 Swiss Table:让内置 map 提速 60% 的秘密
前端
1024小神1 小时前
vue3 + vite项目,如果在build的时候对代码加密混淆
前端·javascript
轻语呢喃1 小时前
useRef :掌握 DOM 访问与持久化状态的利器
前端·javascript·react.js
w_y_fan2 小时前
flutter_riverpod: ^2.6.1 应用笔记 (一)
前端·flutter
Jerry2 小时前
Compose 界面工具包
前端
Focusbe2 小时前
从0到1开发一个AI助手
前端·人工智能·面试