Vue3's responsive special processing mechanism for arrays, maps, and sets, this is the most complex and valuable part of the reactivity system, and it is also a knowledge point that only experienced developers will encounter.
🧠 1. Why do arrays, Maps, and Sets need extra special treatment?
Because of these 3 types of data structures:
- There are non-ordinary attribute access (length, size)
- There are method operations (push, delete, set...)
- There are iterator dependencies (for...of, keys, entries)
- There is implicit reading of itself (accessing length will trigger track)
- There is a complex key → value relationship (Map/Set is not an enumerable key like ordinary objects)
These cannot be handled by simple "get/set".
Vue3 specially wrote handlers for this:
mutableHandlers // 对象
mutableCollectionHandlers // Map/Set 专用
arrayInstrumentations // 数组方法适配👇Let’s talk about it one by one.
🧩 2. Reactive special processing of arrays
The key points of Vue3 for arrays are:
2.1 Separate dependency tracking of array subscript + length
For example:
state.arr.length // 依赖 length
state.arr[1] // 依赖 index=1When executing:
state.arr.push(123)Internally it affects:
- length
- new index (such as arr[3])
Vue3 will:
push/pop/shift/unshift/splice will trigger:
trigger(target, 'length')trigger(target, index)
Because once length is changed, any effect that depends on array.length must be re-executed.
2.2 Prevent recursive triggering in push
The Vue3 source code wraps the array method with a layer (arrayInstrumentations) to prevent this situation:
const arr = reactive([])
effect(() => {
arr.push(1) // 会触发 set → trigger → effect 循环
})Vue will temporarily "turn off" dependency collection internally, and will not track when executing these array methods.
🧩 3. Responsive special processing of Map and Set
Vue3's Map/Set responsive processing is the most essential and complex part of reactivity!
Core difficulties:
| Operations | Features |
|---|---|
| Map.set | Non-covering (new key) vs covering (old key) |
| Map.delete | will change size |
| Map.forEach / for...of | Iteration dependency needs to monitor all element changes |
| Map.get | Depends on key alone |
| Set.add | New elements vs existing elements |
Vue3 specifically implements Map/Set:
mutableCollectionHandlers
get
set
add
delete
clear
iterator methods🧠 4. Key data structures of Map and Set (super important)
Vue’s dependency collection core has two more types:
ITERATE_KEY // for...of、keys()、values() 的依赖
MAP_KEY_ITERATE_KEY // 特别为 Map 的 key 迭代实现Example:
effect(() => {
for (const key of map.keys()) {
console.log(key)
}
})Vue will track:
track(map, ITERATE_KEY)This way: When map adds a new key or deletes a key → trigger ITERATE_KEY → re-execute the iteration effect.
🚀 5. Map.set’s responsive logic (very critical)
Source code logic (simplified version):
set(key, value) {
const hadKey = target.has(key)
const oldValue = target.get(key)
target.set(key, value)
if (!hadKey) {
trigger(target, 'add', key)
} else if (oldValue !== value) {
trigger(target, 'set', key)
}
}Meaning:
- New key: triggers "add" → affects ITERATE_KEY (iterative dependency)
- Old key override: trigger "set" → affects key's dependency
Example:
Case 1: Overwriting key
map.set('a', 1)
effect(() => {
console.log(map.get('a'))
})
map.set('a', 2) // 触发 get('a') 的 effectCase 2: New key triggers iterative update
effect(() => {
for (const [k, v] of map) { }
})
map.set('b', 123)
// 会触发迭代 effect,而不是 get('a')🔥 6. Special logic of Map.delete and clear
delete(key) {
const hadKey = target.has(key)
const result = target.delete(key)
if (hadKey) {
trigger(target, 'delete', key)
}
}delete will trigger:
- Dependency pointing to key
- Iterative dependencies (because key is reduced)
clear() triggers all dependencies:
trigger(target, 'clear')🧩 7. Special logic of Set.add
add(value) {
const had = target.has(value)
target.add(value)
if (!had) {
trigger(target, 'add', value)
}
}Note:
Set.add will not trigger an update if added repeatedly (because there are no changes), Vue will determine this internally.
🌈 8. Special treatment for iterator methods (for…of, keys, values, entries)
Vue3 has a proxy for each iterator method, such as:
for (const item of reactiveMap) { }
for (const [key, val] of reactiveMap.entries()) { }
reactiveMap.forEach((val, key) => { })These are not ordinary get/set and require track:
Map’s iteration dependencies:
track(target, ITERATE_KEY)keys() additional uses:
track(target, MAP_KEY_ITERATE_KEY)This can distinguish:
- Changing key → affects keys()
- Changing value → affects entries() / values()
This is a very subtle aspect of Vue3's design.
🌟 9. Summary: dependency types triggered by different operations
| data structure | operation | track | trigger |
|---|---|---|---|
| Array | get | index / length | |
| push/pop | temporarily disable track | length + index | |
| Map | get | key | |
| set | key / ITERATE_KEY | key or iteration dependency | |
| delete | key + ITERATE_KEY | ||
| for…of | ITERATE_KEY | add/delete | |
| keys() | MAP_KEY_ITERATE_KEY | ||
| Set | add | ITERATE_KEY | add |
| delete | ITERATE_KEY | delete | |
| for…of | ITERATE_KEY | add/delete |
🧨 10. Why can Vue3 accurately determine “which part needs to be updated”?
Relying on a two-layer mechanism:
① track has different types of dependencies
- key dependency
- length depends on
- iterative dependency
- keys iteration dependency
② trigger precise scheduling corresponding effect
For example:
map.set(key, newVal)Only triggers:
- dep of key
- iterator dep (if new)
Irrelevant effects will not be triggered randomly.
This is the key to why Vue3 performs far better than Vue2.