@@ -7,7 +7,14 @@ import {
77 ProxyArrayState
88} from "../internal"
99
10- // Type-safe union of mutating array method names
10+ /**
11+ * Methods that directly modify the array in place.
12+ * These operate on the copy without creating per-element proxies:
13+ * - `push`, `pop`: Add/remove from end
14+ * - `shift`, `unshift`: Add/remove from start (marks all indices reassigned)
15+ * - `splice`: Add/remove at arbitrary position (marks all indices reassigned)
16+ * - `reverse`, `sort`: Reorder elements (marks all indices reassigned)
17+ */
1118type MutatingArrayMethod =
1219 | "push"
1320 | "pop"
@@ -17,7 +24,22 @@ type MutatingArrayMethod =
1724 | "reverse"
1825 | "sort"
1926
20- // Type-safe union of non-mutating array method names
27+ /**
28+ * Methods that read from the array without modifying it.
29+ * These fall into distinct categories based on return semantics:
30+ *
31+ * **Subset operations** (return drafts - mutations propagate):
32+ * - `filter`, `slice`: Return array of draft proxies
33+ * - `find`, `findLast`: Return single draft proxy or undefined
34+ *
35+ * **Transform operations** (return base values - mutations don't track):
36+ * - `concat`, `flat`: Create new structures, not subsets of original
37+ *
38+ * **Primitive-returning** (no draft needed):
39+ * - `findIndex`, `findLastIndex`, `indexOf`, `lastIndexOf`: Return numbers
40+ * - `some`, `every`, `includes`: Return booleans
41+ * - `join`, `toString`, `toLocaleString`: Return strings
42+ */
2143type NonMutatingArrayMethod =
2244 | "filter"
2345 | "slice"
@@ -29,18 +51,51 @@ type NonMutatingArrayMethod =
2951 | "findLastIndex"
3052 | "some"
3153 | "every"
32- | "reduce"
33- | "reduceRight"
3454 | "indexOf"
3555 | "lastIndexOf"
3656 | "includes"
3757 | "join"
3858 | "toString"
3959 | "toLocaleString"
4060
41- // Union of all array operation methods
61+ /** Union of all array operation methods handled by the plugin. */
4262export type ArrayOperationMethod = MutatingArrayMethod | NonMutatingArrayMethod
4363
64+ /**
65+ * Enables optimized array method handling for Immer drafts.
66+ *
67+ * This plugin overrides array methods to avoid unnecessary Proxy creation during iteration,
68+ * significantly improving performance for array-heavy operations.
69+ *
70+ * **Mutating methods** (push, pop, shift, unshift, splice, sort, reverse):
71+ * Operate directly on the copy without creating per-element proxies.
72+ *
73+ * **Non-mutating methods** fall into categories:
74+ * - **Subset operations** (filter, slice, find, findLast): Return draft proxies - mutations track
75+ * - **Transform operations** (concat, flat): Return base values - mutations don't track
76+ * - **Primitive-returning** (indexOf, includes, some, every, etc.): Return primitives
77+ *
78+ * **Important**: Callbacks for overridden methods receive base values, not drafts.
79+ * This is the core performance optimization.
80+ *
81+ * @example
82+ * ```ts
83+ * import { enableArrayMethods, produce } from "immer"
84+ *
85+ * enableArrayMethods()
86+ *
87+ * const next = produce(state, draft => {
88+ * // Optimized - no proxy creation per element
89+ * draft.items.sort((a, b) => a.value - b.value)
90+ *
91+ * // filter returns drafts - mutations propagate
92+ * const filtered = draft.items.filter(x => x.value > 5)
93+ * filtered[0].value = 999 // Affects draft.items[originalIndex]
94+ * })
95+ * ```
96+ *
97+ * @see https://immerjs.github.io/immer/array-methods
98+ */
4499export function enableArrayMethods ( ) {
45100 const SHIFTING_METHODS = new Set < MutatingArrayMethod > ( [ "shift" , "unshift" ] )
46101
@@ -134,7 +189,15 @@ export function enableArrayMethods() {
134189 return Math . min ( index , length )
135190 }
136191
137- // Consolidated handler functions
192+ /**
193+ * Handles mutating operations that add/remove elements (push, pop, shift, unshift, splice).
194+ *
195+ * Operates directly on `state.copy_` without creating per-element proxies.
196+ * For shifting methods (shift, unshift), marks all indices as reassigned since
197+ * indices shift.
198+ *
199+ * @returns For push/pop/shift/unshift: the native method result. For others: the draft.
200+ */
138201 function handleSimpleOperation (
139202 state : ProxyArrayState ,
140203 method : string ,
@@ -155,6 +218,15 @@ export function enableArrayMethods() {
155218 } )
156219 }
157220
221+ /**
222+ * Handles reordering operations (reverse, sort) that change element order.
223+ *
224+ * Operates directly on `state.copy_` and marks all indices as reassigned
225+ * since element positions change. Does not mark length as changed since
226+ * these operations preserve array length.
227+ *
228+ * @returns The draft proxy for method chaining.
229+ */
158230 function handleReorderingOperation (
159231 state : ProxyArrayState ,
160232 method : string ,
@@ -171,12 +243,29 @@ export function enableArrayMethods() {
171243 ) // Don't mark length as changed
172244 }
173245
246+ /**
247+ * Creates an interceptor function for a specific array method.
248+ *
249+ * The interceptor wraps array method calls to:
250+ * 1. Set `state.operationMethod` flag during execution (allows proxy `get` trap
251+ * to detect we're inside an optimized method and skip proxy creation)
252+ * 2. Route to appropriate handler based on method type
253+ * 3. Clean up the operation flag in `finally` block
254+ *
255+ * The `operationMethod` flag is the key mechanism that enables the proxy's `get`
256+ * trap to return base values instead of creating nested proxies during iteration.
257+ *
258+ * @param state - The proxy array state
259+ * @param originalMethod - Name of the array method being intercepted
260+ * @returns Interceptor function that handles the method call
261+ */
174262 function createMethodInterceptor (
175263 state : ProxyArrayState ,
176264 originalMethod : string
177265 ) {
178266 return function interceptedMethod ( ...args : any [ ] ) {
179- // Enter operation mode
267+ // Enter operation mode - this flag tells the proxy's get trap to return
268+ // base values instead of creating nested proxies during iteration
180269 const method = originalMethod as ArrayOperationMethod
181270 enterOperation ( state , method )
182271
@@ -203,12 +292,38 @@ export function enableArrayMethods() {
203292 return handleNonMutatingOperation ( state , method , args )
204293 }
205294 } finally {
206- // Always exit operation mode
295+ // Always exit operation mode - must be in finally to handle exceptions
207296 exitOperation ( state )
208297 }
209298 }
210299 }
211300
301+ /**
302+ * Handles non-mutating array methods with different return semantics.
303+ *
304+ * **Subset operations** return draft proxies for mutation tracking:
305+ * - `filter`, `slice`: Return `state.draft_[i]` for each selected element
306+ * - `find`, `findLast`: Return `state.draft_[i]` for the found element
307+ *
308+ * This allows mutations on returned elements to propagate back to the draft:
309+ * ```ts
310+ * const filtered = draft.items.filter(x => x.value > 5)
311+ * filtered[0].value = 999 // Mutates draft.items[originalIndex]
312+ * ```
313+ *
314+ * **Transform operations** return base values (no draft tracking):
315+ * - `concat`, `flat`: These create NEW arrays rather than selecting subsets.
316+ * Since the result structure differs from the original, tracking mutations
317+ * back to specific draft indices would be impractical/impossible.
318+ *
319+ * **Primitive operations** return the native result directly:
320+ * - `indexOf`, `includes`, `some`, `every`, `join`, etc.
321+ *
322+ * @param state - The proxy array state
323+ * @param method - The non-mutating method name
324+ * @param args - Arguments passed to the method
325+ * @returns Drafts for subset operations, base values for transforms, primitives otherwise
326+ */
212327 function handleNonMutatingOperation (
213328 state : ProxyArrayState ,
214329 method : NonMutatingArrayMethod ,
@@ -264,8 +379,11 @@ export function enableArrayMethods() {
264379 return result
265380 }
266381
267- // For other methods (indexOf, includes, join, etc.), call on base array
268- // These don't need drafts since they don't expose items for mutation
382+ // For other methods, call on base array directly:
383+ // - indexOf, includes, join, toString: Return primitives, no draft needed
384+ // - concat, flat: Return NEW arrays (not subsets). Elements are base values.
385+ // This is intentional - concat/flat create new data structures rather than
386+ // selecting subsets of the original, making draft tracking impractical.
269387 return source [ method as keyof typeof Array . prototype ] ( ...args )
270388 }
271389
0 commit comments