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

无需深拷贝,React useState钩子触发重渲染的惯用方式是什么?

React状态更新与重渲染的正确处理方式

先看这段存在问题的函数式组件:

function Test() {
  const [data, setData] = React.useState(new Set());

  function handleClick() {
    data.add(42);
    setData(data);
  }

  return (
    <>
      <button onClick={handleClick}>ADD 42</button>
      <p>{data.has(42) ? 'in set' : 'not in set'}</p>
    </>
  );
}

这段代码的核心问题是:点击按钮后组件不会重渲染。原因是React判断状态是否需要更新的依据是引用是否改变——你虽然修改了Set内部的内容,但setData(data)传递的还是同一个Set对象的引用,React会认为状态没有变化,因此不会触发重渲染。

有人会用structuredClone做深拷贝来强制更新,比如:

function handleClick() {
  data.add(42);
  setData(structuredClone(data));
}

但这种方案并不理想:深拷贝会带来不必要的性能开销,而且代码意图模糊,其他开发者很难一眼看出你是为了触发重渲染才做的拷贝。


惯用的正确解决方案:创建新的引用

React的状态更新规范要求你传递新的引用。对于Set这类集合,正确的做法是基于原Set创建一个新的Set实例,同时包含新增的元素:

简洁版写法:

function handleClick() {
  setData(new Set([...data, 42]));
}

高效版写法(避免展开数组的额外开销):

function handleClick() {
  const newSet = new Set(data);
  newSet.add(42);
  setData(newSet);
}

这种方式的优势很明显:

  • 明确传递了新的引用,React会立刻识别到状态变化并触发重渲染
  • 代码意图清晰,一看就知道是在更新Set类型的状态
  • 性能远优于深拷贝,只是创建新Set并复制原元素,没有全量深拷贝的额外消耗

模拟「markAsDirty」的反模式方案

如果一定要实现类似markAsDirty的写法(不推荐,不符合React状态更新范式),可以额外维护一个用于触发重渲染的状态标记:

function Test() {
  const [data, setData] = React.useState(new Set());
  const [, forceRender] = React.useState({});

  const markAsDirty = () => {
    forceRender({}); // 传递新的空对象,触发重渲染
  };

  function handleClick() {
    data.add(42);
    markAsDirty();
  }

  return (
    <>
      <button onClick={handleClick}>ADD 42</button>
      <p>{data.has(42) ? 'in set' : 'not in set'}</p>
    </>
  );
}

注意:这种方式属于反模式,它绕过了React的状态更新机制直接强制重渲染,会让状态与UI的同步关系变得模糊,后续维护容易出现难以排查的问题。除非遇到极特殊的场景,否则优先使用「创建新引用更新状态」的方式。

内容的提问来源于stack exchange,提问作者Vittorio Romeo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 15:08:19