数组方法插件
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
概述
数组方法插件 (enableArrayMethods()) 通过避免迭代过程中不必要的 Proxy 创建,优化了 Immer producer 中的数组操作。对于数组密集型操作可带来显著的性能提升。
这为何重要? 未启用插件时,迭代过程中的每次数组元素访问(例如在 filter、find、slice 中)都会创建 Proxy 对象。对于含 1000 个元素的数组,仅迭代就会触发 1000+ 次代理陷阱调用。启用插件后,回调函数接收基础值(非代理值),且仅在需要变更追踪时才创建代理。
安装
在应用入口点一次性启用插件:
import {enableArrayMethods} from "immer"
enableArrayMethods()
这将增加约 2KB 的打包体积。
变更型方法
以下方法直接修改原数组,操作时直接作用于草稿的内部副本而无需创建逐元素代理:
| Method | Returns | Description |
|---|---|---|
push() | New length | Adds elements to the end |
pop() | Removed element | Removes and returns the last element |
shift() | Removed element | Removes and returns the first element |
unshift() | New length | Adds elements to the beginning |
splice() | Removed elements | Adds/removes elements at any position |
sort() | The draft array | Sorts elements in place |
reverse() | The draft array | Reverses the array in place |
import {produce, enableArrayMethods} from "immer"
enableArrayMethods()
const base = {items: [3, 1, 4, 1, 5]}
const result = produce(base, draft => {
draft.items.push(9) // Adds 9 to end
draft.items.sort() // Sorts: [1, 1, 3, 4, 5, 9]
draft.items.reverse() // Reverses: [9, 5, 4, 3, 1, 1]
})
非变更型方法
非变更型方法根据返回值分为三类:
子集操作(返回草稿)
这些方法筛选原数组中存在的项,并为返回项创建草稿代理。回调接收基础值(优化点),但返回的数组包含指向原始位置的新建草稿代理。对返回项的修改将影响草稿状态。
| Method | Returns | Drafts? |
|---|---|---|
filter() | Array of matching items | ✅ Yes |
slice() | Array of items in range | ✅ Yes |
find() | First matching item or undefined | ✅ Yes |
findLast() | Last matching item or undefined | ✅ Yes |
const base = {
items: [
{id: 1, value: 10},
{id: 2, value: 20},
{id: 3, value: 30}
]
}
const result = produce(base, draft => {
// filter returns drafts - mutations track back to original
const filtered = draft.items.filter(item => item.value > 15)
filtered[0].value = 999 // This WILL affect draft.items[1]
// find returns a draft - mutations track back
const found = draft.items.find(item => item.id === 3)
if (found) {
found.value = 888 // This WILL affect draft.items[2]
}
// slice returns drafts
const sliced = draft.items.slice(0, 2)
sliced[0].value = 777 // This WILL affect draft.items[0]
})
console.log(result.items[0].value) // 777
console.log(result.items[1].value) // 999
console.log(result.items[2].value) // 888
转换操作(返回基础值)
这些方法创建新数组(可能包含外部项或重构数据),返回基础值而非草稿。对返回项的修改不会追溯至草稿状态。
| Method | Returns | Drafts? |
|---|---|---|
concat() | New combined array | ❌ No |
flat() | New flattened array | ❌ No |
const base = {items: [{id: 1, value: 10}]}
const result = produce(base, draft => {
// concat returns base values - mutations DON'T track
const concatenated = draft.items.concat([{id: 2, value: 20}])
concatenated[0].value = 999 // This will NOT affect draft.items[0]
// To actually use concat results, assign them:
draft.items = draft.items.concat([{id: 2, value: 20}])
})
// Original unchanged because concat result wasn't assigned
console.log(result.items[0].value) // 10 (unchanged)
为何区分处理?
子集操作 (
filter,slice,find) 筛选原数组中存在的项。返回草稿可使修改传递回源数据转换操作 (
concat,flat) 创建可能包含外部项或重构数据的新数据结构,使草稿追踪不可行
返回基础类型的方法
以下方法返回基础类型值(数字、布尔值、字符串)。由于基础类型不可草稿化,不存在追踪问题:
| Method | Returns |
|---|---|
indexOf() | Number (index or -1) |
lastIndexOf() | Number (index or -1) |
includes() | Boolean |
some() | Boolean |
every() | Boolean |
findIndex() | Number (index or -1) |
findLastIndex() | Number (index or -1) |
join() | String |
toString() | String |
toLocaleString() | String |
const base = {
items: [
{id: 1, active: true},
{id: 2, active: false}
]
}
const result = produce(base, draft => {
const index = draft.items.findIndex(item => item.id === 2)
const hasActive = draft.items.some(item => item.active)
const allActive = draft.items.every(item => item.active)
console.log(index) // 1
console.log(hasActive) // true
console.log(allActive) // false
})
未覆盖的方法
以下方法未被插件拦截,通过标准 Proxy 机制运作。回调接收草稿,变更追踪正常进行:
| Method | Description |
|---|---|
map() | Transform each element |
flatMap() | Map then flatten |
forEach() | Execute callback for each element |
reduce() | Reduce to single value |
reduceRight() | Reduce from right to left |
const base = {
items: [
{id: 1, value: 10, nested: {count: 0}},
{id: 2, value: 20, nested: {count: 0}}
]
}
const result = produce(base, draft => {
// forEach receives drafts - mutations work normally
draft.items.forEach(item => {
item.value *= 2
})
// map is NOT overridden - callbacks receive drafts
// The returned array items are also drafts (extracted from draft.items)
const mapped = draft.items.map(item => item.nested)
// Mutations to the result array propagate back
mapped[0].count = 999 // ✅ This affects draft.items[0].nested.count
})
console.log(result.items[0].nested.count) // 999
回调行为
对于覆盖的方法,回调接收基础值(非草稿)。这是核心优化——避免在迭代期间为每个元素创建代理。
const base = {
items: [
{id: 1, value: 10},
{id: 2, value: 20}
]
}
produce(base, draft => {
draft.items.filter(item => {
// `item` is a base value here, NOT a draft
// Reading properties works fine
return item.value > 15
// But direct mutation here won't be tracked:
// item.value = 999 // ❌ Won't affect draft
})
// Instead, use the returned draft:
const filtered = draft.items.filter(item => item.value > 15)
filtered[0].value = 999 // ✅ This works because filtered[0] is a draft
})
方法返回行为摘要
| Category | Methods | Returns | Mutations Track? |
|---|---|---|---|
| Subset | filter, slice, find, findLast | Draft proxies | ✅ Yes |
| Transform | concat, flat | Base values | ❌ No |
| Primitive | indexOf, includes, some, every, findIndex, findLastIndex, lastIndexOf, join, toString, toLocaleString | Primitives | N/A |
| Mutating | push, pop, shift, unshift, splice, sort, reverse | Various | ✅ Yes (modifies draft) |
| Not Overridden | map, flatMap, forEach, reduce, reduceRight | Standard behavior | ✅ Yes (callbacks get drafts) |
使用场景
在以下情况启用数组方法插件:
应用在 producer 中有大量数组迭代操作
频繁对大数组使用
filter,find,some,every等方法性能分析显示数组操作是瓶颈
该插件在以下场景收益最大:
大型数组(100+ 元素)
频繁调用含数组操作的 producer
读密集型操作(筛选、搜索)且多数元素未被修改
性能收益
未启用插件时:
迭代期间每次数组元素访问都创建 Proxy
对 1000 元素执行
filter()= 1000+ 次代理创建
启用插件后:
回调直接接收基础值
仅在实际发生变动的特定元素或匹配过滤条件的元素上创建代理(Proxies)
// Without plugin: ~3000+ proxy trap invocations
// With plugin: ~10-20 proxy trap invocations
const result = produce(largeState, draft => {
const filtered = draft.items.filter(x => x.value > threshold)
// Only items you mutate get proxied
filtered.forEach(item => {
item.processed = true
})
})