array.prototype.with 是 es2023 引入的安全替换数组指定索引元素的方法,返回新数组且不修改原数组,语义清晰、性能更优,但仅适用于单元素替换且需注意浏览器兼容性与越界防护。

Array.prototype.with 是 ECMAScript 2023(ES14)引入的新增数组方法,用于**安全、简洁地替换指定索引处的元素,返回新数组**,不修改原数组。它天然契合 React 中“不可变更新”的核心要求,但需注意:它**仅适用于数组**,且目前浏览器兼容性有限(Chrome 111+、Edge 111+、Firefox 117+、Safari 17.4+),尚未进入所有主流环境。
为什么 with 比 slice + 替换更直观?
传统方式更新数组第 i 项常写成:
setItems(prev => prev.map((item, idx) => idx === i ? newItem : item))(全量映射,性能不优)
或
setItems(prev => [...prev.slice(0, i), newItem, ...prev.slice(i + 1)])(三次拷贝,可读性差)
with 一行解决:
setItems(prev => prev.with(i, newItem))
语义清晰、代码简短、内部实现通常更高效(V8 已优化为紧凑拷贝)。
在 React 中安全使用的前提条件
- 确保运行时环境支持 Array.prototype.with(开发时可用
@babel/preset-env+core-js按需 polyfill,但注意 polyfill 会降级为 slice 方案,失去原生性能优势) - 只用于**单个元素替换**场景;若需插入、删除或多位置更新,仍应使用 toSpliced、toReversed 等配套新方法,或保持原有逻辑
- React 组件中仍需配合 useState 或 useReducer 使用,with 本身不触发重渲染,只是生成新数组的工具
典型使用示例(带边界防护)
避免越界导致静默失败(with(-1, x) 或 with(999, x) 在越界时返回原数组,可能掩盖 bug):
const replaceItem = (index, newValue) => {
setItems(prev => {
if (index = prev.length) return prev; // 显式防护
return prev.with(index, newValue);
});
};
搭配函数式更新,确保基于最新状态(尤其在异步或快速连续调用时):
// ✅ 正确:基于上一状态计算 setItems(prev => prev.with(targetIndex, updatedValue)); // ❌ 避免:捕获过期的 prev const captured = items; setItems(captured.with(targetIndex, updatedValue)); // 可能出错
与其它不可变操作的协同
with 不替代 map 或 filter,而是补充特定场景。例如:
- 切换列表某项的 isEditing 状态:
items.with(i, {...items[i], isEditing: true}) - 更新表单项值:
formFields.with(fieldIndex, { ...field, value: e.target.value }) - 配合 toSorted 实现排序后更新:
items.toSorted(...).with(0, pinnedItem)
注意:所有这些操作都返回新数组,React 的 useState 会自动识别引用变化并触发更新(前提是父组件未错误 memo 化)。











