React 纠错指南:直接改 state 而不是用 setState

—— 从“页面不更新”到“数据被覆盖”一次说清

一、为什么必须 setState?

React 的核心机制:

  1. 状态是不可变的
    this.state / useState 返回的是快照,直接改对象不会触发重新渲染。

  2. 批处理与调和
    只有 setState / setXxx 会把变更加入队列,统一批量更新 DOM。

  3. 时间旅行与调试
    直接突变会让 DevTools、Redux、水合检测全部失效。


二、3 个高频翻车现场

场景错误写法现象
类组件this.state.list.push(item)页面不更新
函数组件arr[0] = 1下一次 setState 把旧值覆盖回来
嵌套对象obj.a.b = newVal调试工具看不到变化

三、正确 vs 错误对比(可copy)

1️⃣ 类组件

// ❌ 直接突变
this.state.list.push(item);

// ✅  immutable 更新
this.setState(prev => ({
  list: [...prev.list, item]
}));

2️⃣ 函数组件

// ❌ 直接改数组
const [list, setList] = useState([]);
list.push(item);        // 不触发渲染
setList(list);          // 仍指向同一引用,React 跳过更新

// ✅ 生成新引用
setList(prev => [...prev, item]);

3️⃣ 深层对象

// ❌ 直接改嵌套属性
const [user, setUser] = useState({ profile: { name: '' } });
user.profile.name = 'Tom';   // 突变
setUser(user);               // 引用未变,不更新

// ✅ 逐层展开
setUser(prev => ({
  ...prev,
  profile: { ...prev.profile, name: 'Tom' }
}));

四、批量更新陷阱

// ❌ 三次直接修改,只会触发一次渲染,且数据被覆盖
const [count, setCount] = useState(0);
count++;          // 0 → 1
count++;          // 仍 1(引用未变)
setCount(count);  // 最终 1

// ✅ 函数式更新,每次基于最新状态
setCount(c => c + 1);
setCount(c => c + 1);
// 最终 2

五、Immutable 工具链(可选)

工具示例
Immerproduce(draft => { draft.list.push(item) })
use-immerconst [state, update] = useImmer(initial)
lodash cloneDeepsetState(_.cloneDeep(obj))

六、一句话总结

永远把 state 当成只读快照。
任何变更都通过 setState / setXxx 并返回新引用,React 才会“听到”你的声音。


最后问候亲爱的朋友们,并邀请你们阅读我的全新著作

📚 《 React开发实践:掌握Redux与Hooks应用 》

在这里插入图片描述

更多推荐