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 */ }
相关推荐
Hexene...7 分钟前
【前端Vue】npm install时根据新的状态重新引入实际用到的包,不引入未使用到的
前端·vue.js·npm
2301_7806698612 分钟前
Vue(入门配置、常用指令)、Ajax、Axios
前端·vue.js·ajax·javaweb
码农幻想梦13 分钟前
Vue3入门到实战【尚硅谷】
前端·vue
hudou_k15 分钟前
利用WebNaket实现Web应用直接访问硬件设备
前端
吃茄子的猫15 分钟前
若依框架根据当前登录人信息,显示不同的静态公司logo
前端·vue
LZQ <=小氣鬼=>24 分钟前
React + Ant Design (antd) 国际化完整实战教程
前端·react.js·前端框架·antd·moment
星海拾遗27 分钟前
react源码从入门到入定
前端·javascript·react.js
Charlie_lll30 分钟前
学习Three.js–星环粒子(ShaderMaterial)
前端·three.js
wuhen_n34 分钟前
JavaScript事件循环(下) - requestAnimationFrame与Web Workers
开发语言·前端·javascript
我是ed.36 分钟前
Vue3 音频标注插件 wavesurfer
前端·vue.js·音视频