Array.prototype.with() 是 ES2023 引入的不可变数组更新方法,通过 arr.with(index, value) 安全替换指定索引元素,返回新数组,支持负索引、越界报错、浅拷贝,性能优于 map 和 slice 拼接,适用于 Redux 等状态管理场景。

Array.prototype.with() 是 ES2023 正式引入的原生方法,专为不可变地替换数组中**单个指定索引位置的元素**而设计。它不修改原数组,而是返回一个新数组,天然满足不可变性要求——这是 React、Redux、Vuex 等强调状态不可变的场景所依赖的核心特性。
直接替换某一位,语义清晰且安全
调用 arr.with(index, newValue) 即可完成局部更新,无需切片、拼接或遍历:
- 原数组完全不变,引用未被复用,避免副作用
- 只改变目标索引处的值,其余元素保持原引用(浅拷贝)
- 越界索引(如 index ≥ arr.length 或 index < -arr.length)会立即抛出
RangeError,而非静默失败,利于早期发现问题 - 支持负索引:例如
[1,2,3].with(-1, 9)→[1,2,9]
在状态管理中自然落地(如 Redux Toolkit)
在 createSlice 的 reducer 中可直接使用,无需额外 clone 或 Immer 介入:
-
合法写法:
state.items = state.items.with(action.payload.index, updatedItem); - 返回的新数组自动触发 shallowEqual 检测,确保 UI 正确响应
- 比
map()更轻量:不遍历全数组,引擎已做底层优化(V8/Firefox 均有快速路径) - 注意:不能替代按 ID 更新逻辑;若列表顺序动态变化,硬编码索引易错,应优先结合 ID 查找
和旧写法对比:更简、更稳、更省
过去常用方式存在冗余或隐患:
-
[...arr.slice(0,i), newValue, ...arr.slice(i+1)]:创建多个中间数组,内存开销大,稀疏数组行为不一致 -
arr.map((v, idx) => idx === i ? newValue : v):强制全量遍历,语义模糊,易漏判索引条件 -
Object.assign([], arr, {[i]: newValue}):对 getter/setter 或自定义属性兼容性差 -
with():一行解决,意图明确,性能更优,且对 Symbol.isConcatSpreadable 等边界情况更鲁棒
需避开的典型误区
它能力明确,但边界严格:
- 空数组无法调用:
[].with(0, 'x')报错(因 0 不满足-0 ≤ 0 < 0) - 不支持条件替换(如“把第一个大于 5 的数改成 0”),这类仍需
findIndex + with组合 - 不支持多位置一次更新:要改两个索引,得链式调用
arr.with(i1, a).with(i2, b) - 不扩展数组长度:
arr.with(arr.length, x)会报错;末尾追加请用toSpliced(arr.length, 0, x)

















