You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中手动修改状态为何是反模式?附复杂状态场景

为什么不推荐这种React状态修改的反模式?

假设我们有一个包含大量复制成本高昂字段的复杂状态,需要按特定顺序更新,常规写法如下:

setState({
  ...myComplexState,
  expensiveFieldA: newA,
});

// 计算newB的逻辑

setState({
  ...myComplexState,
  expensiveFieldB: newB,
});

这种写法会触发多次重渲染,还会在复制未更改字段上浪费CPU资源,因此有人想出了下面这种直接修改状态再手动触发更新的模式:

import { useState } from 'react';

class StateWrapper<S> {
  state: S;

  constructor(state: S) {
    this.state = state;
  }

  shallowCopy() {
    return new StateWrapper(this.state);
  }
}

function useObjState<T>(initState: T): [T, () => void] {
  const [wrapper, setWrapper] = useState(() => new StateWrapper(initState));
  const commit = () => setWrapper(wrapper.shallowCopy());

  return [wrapper.state, commit];
}

class ExpensiveState {
  private val: string;

  constructor() {
    this.val = '';
  }

  preformExpensiveOperations(val: string) {
    this.val = val;
  }

  preformAnotherExpensiveOperation() {}

  getVal() {
    return this.val;
  }
}

function App() {
  const [state, commit] = useObjState(new ExpensiveState());

  return (
    <>
      <p>val: {state.getVal()}</p>
      <p>
        <input
          onChange={e => {
            state.preformExpensiveOperations(e.target.value);
            state.preformAnotherExpensiveOperation();
            commit();
          }}
        />
      </p>
    </>
  );
}

export default App;

虽然这种写法看似解决了性能问题,但它是明确的反模式,原因如下:

1. 违反React的不可变状态原则

React的更新机制完全依赖状态引用的变化来识别状态更新。直接修改状态对象的内部值,状态的引用并没有改变,这会导致依赖状态的优化逻辑(比如React.memo、shouldComponentUpdate)失效——这些逻辑会认为状态没有变化,从而跳过必要的重渲染,最终造成UI和实际状态不一致。

2. 并发渲染下的状态一致性风险

React 18+引入了并发渲染模式,渲染过程可能被中断、暂停或重启。直接修改同一个状态引用时,不同渲染周期可能拿到被篡改的中间状态,导致UI出现不可预测的错误,比如显示过时的数据、操作结果异常等。

3. 调试难度指数级上升

React DevTools依赖状态的不可变性来记录每一次状态变更的历史。直接修改状态的话,DevTools无法追踪到状态的具体修改过程,你看不到状态从A到B的变化轨迹,排查bug时只能盲目猜测,效率极低。

4. 破坏组件的复用与可维护性

这种写法把状态的修改逻辑耦合在自定义类中,和React函数式组件的设计理念相悖。其他组件如果要复用这个状态,很难进行扩展,而且状态的修改过程完全不透明,违背了React单向数据流的原则,长期来看会让代码的维护成本急剧上升。

5. 隐藏的性能隐患

看似减少了对象复制的成本,但实际上这种写法会让React的更新逻辑混乱。比如多次修改状态后调用commit,可能因为状态引用的问题导致意外的重渲染;或者因为状态未被正确标记为更新,需要额外的渲染来修正UI,反而比常规写法的性能更差。

内容的提问来源于stack exchange,提问作者Winston Moxley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 18:12:40