Array.prototype.with 是 ES14 新增的不可变数组更新方法,通过 with(i, value) 简洁替换指定索引元素,返回新数组,契合 React 纯函数状态更新理念,比 slice/map 展开更语义化、高效且安全。

Array.prototype.with 是 ECMAScript 2023(ES14)新增的数组方法,用于**不可变地替换指定索引处的元素**,返回一个新数组,不修改原数组。它天然契合 React 中“纯函数更新状态”的理念——避免直接修改数组、不依赖副作用、可预测、易测试。
为什么用 .with 而不是 slice + 替换或 [...arr]?
传统方式如 [...arr.slice(0, i), newValue, ...arr.slice(i + 1)] 或 arr.map((v, idx) => idx === i ? newValue : v) 写法冗长、可读性差,且在深层嵌套或条件复杂时容易出错。.with(i, newValue) 更简洁、语义明确、性能更优(V8 等引擎已做底层优化),且与 React 的不可变更新哲学高度一致。
基础用法:替换单个元素
适用于列表项编辑、开关切换、排序后重置某位等场景:
- 假设 state 是
const [items, setItems] = useState(['a', 'b', 'c']) - 点击第 2 项(索引 1)切换为 'B':
setItems(items.with(1, 'B'))→['a', 'B', 'c'] - 安全处理越界索引:索引超出范围时抛出
RangeError,建议配合Math.min(i, items.length - 1)或 try/catch(实际项目中通常索引可控,无需过度防御)
组合更新:配合 map、filter 实现复合逻辑
.with 本身只支持单索引替换,但可与其他不可变方法链式组合,实现更复杂的状态流:
- 更新满足条件的第一个匹配项:
const idx = items.findIndex(item => item.id === targetId);<br> setItems(idx >= 0 ? items.with(idx, { ...items[idx], done: true }) : items); - 批量更新多个位置(需多次 with,注意顺序):
setItems(items.with(0, 'x').with(2, 'z'))—— 注意:后一次 with 基于前一次返回的新数组,非原始数组 - 与 filter 配合实现“删除 + 插入”逻辑(如拖拽插入):
const withoutOld = items.filter((_, i) => i !== fromIdx);<br> const withNew = withoutOld.with(toIdx > fromIdx ? toIdx - 1 : toIdx, movedItem);
与 React 状态更新结合的最佳实践
避免常见陷阱,让更新真正“纯”且高效:
- 不要在渲染中直接调用 .with:确保只在事件处理器或 useCallback 中使用,防止每次渲染都生成新数组导致子组件误更新
-
更新依赖最新状态时,用函数式更新:
setItems(prev => prev.with(i, newValue))—— 避免闭包捕获过期 prev -
深度嵌套数组更新?先解构再 with:
若 state 是{ list: [{ id: 1, tags: ['a', 'b'] }] },更新 tags 的第 0 项:setState(prev => ({<br> ...prev,<br> list: prev.list.map((item, idx) =><br> idx === targetIndex<br> ? { ...item, tags: item.tags.with(0, 'X') }<br> : item<br> )<br>})) -
性能提示:.with 返回的是新数组引用,React.memo / useMemo 可据此正确跳过重渲染;但若数组很大且频繁更新,考虑用
useReducer+ immer 等方案权衡可读性与性能
不复杂但容易忽略:.with 是数组专属,对象更新仍需结构赋值或工具库;但它让“数组局部更新”这件事第一次拥有了原生、简洁、语义化的标准写法,是 React 函数式状态管理演进中的自然拼图。


















