ES2023 规范现在提供了四种数组方法——toSortedtoReversedtoSplicedwith——它们返回新数组而不是修改原数组。在依赖不可变状态(immutable state)的 React 和其他 UI 库中,这些助手方法让开发者能够取代长期以来成为 Bug 和样板代码来源的展开运算符(spread operator)技巧。

为什么这一变化至关重要

React 通过比较前一个状态的引用与新状态的引用来决定是否重新渲染组件。如果引用未改变,React 会认为没有任何变化。经典的 Array.prototype.sort 方法会原地(in place)排序并返回相同的引用,因此像 setTasks(prev => prev.sort(fn)) 这样的调用会让 React 无法察觉到更新。UI 会停留在陈旧的数据上,这种 Bug 在实际项目中非常常见。

开发者通常通过先克隆数组(通常使用展开运算符)来解决这个问题,从而使排序步骤产生一个新的引用:

setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));

这种模式虽然有效,但增加了代码冗余且容易被遗忘。新的 ES2023 方法提供了一种直接、可读的方式来生成新数组,同时保持原数组不变。

四种非破坏性方法

  • toSorted(compareFn?) – 行为类似于 sort,但返回排序后的副本。原数组不会改变。
  • toReversed() – 取代 reverse。它产生一个反转后的副本,保持原顺序不变。
  • toSpliced(start, deleteCount, ...items) – 镜像 splice 的功能但没有副作用。返回的数组反映了插入或删除操作,而原数组保持不变。
  • with(index, value) – 用 value 替换 index 处的元素并返回一个新数组。它取代了常见的 map 或展开运算符替换元素的模式。

所有这四种方法都是 ECMAScript 标准的一部分,并可在当前主流浏览器和 Node.js 20 版本中使用。

代码现在的样子

排序列表

// Before
setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));

// After
setTasks(prev => prev.toSorted((a, b) => b.priority - a.priority));

更新单个项目

// Before
setItems(prev =>
  prev.map((item, i) => (i === idx ? newItem : item))
);

// After
setItems(prev => prev.with(idx, newItem));

反转数组

setLogs(prev => prev.toReversed());

删除元素

setTags(prev => prev.toSpliced(removeIdx, 1));

新语法消除了对额外展开运算符或映射循环(mapping loops)的需求,使状态更新更易读且更不易出错。

谁能受益,谁可能会犹豫

使用 React、Vue、Redux、Zustand 或任何期望不可变数据结构的框架的开发者将获得更清晰的心智模型:调用方法,获取新数组,将其传回 setter。样板代码的减少还可以缩短渲染周期中的几毫秒,因为引擎避免了在排序前创建中间副本。

需要支持旧版浏览器的团队可能需要引入 polyfills。这些方法在旧版本的 Safari 或 Internet Explorer 中并不存在,因此针对这些平台的生产构建必须包含回退方案(fallback)。这会增加少量的包体积(bundle-size)开销,但权衡之下,带来的可读性提升通常是值得的。

库作者可能需要更新类型定义(例如 TypeScript)以暴露新的签名。在这些定义进入官方的 @types 包之前,开发者可能会遇到暂时的类型错误。

后续值得关注的方向

  • 采用指标 – 诸如 ESLint 之类的工具可能很快就会添加规则,标记状态 setter 中的可变数组调用,从而引导开发者使用新方法。
  • 性能研究 – 早期基准测试表明,原生的非破坏性方法比“先用展开运算符克隆再进行可变操作”更快,但实际数据将证实其影响。
  • 进一步提案 – ECMAScript 委员会正在继续探索“默认不可变”的 API;关注即将到来的阶段可能会发现更多符合相同模式的助手方法。

总结

最新的 ECMAScript 规范为 UI 开发者提供了一种内置且简洁的方式来保持状态的不可变性,而无需进行那些导致无数 Bug 的展开运算符繁琐操作。通过将 sortreversesplice 和基于索引的替换更换为 toSortedtoReversedtoSplicedwith,你可以让 React 的变更检测按预期工作,并使代码更易读。如果你的目标浏览器支持这些新方法——或者你愿意使用 polyfill——那么是时候淘汰旧模式,让语言来承担繁重的工作了。