vue2 vs vue3
The core difference between the two responsive updates isand
Proxy
Two API problems, through these two APIs, the main responsive problems can be solved. For some cases, special treatment is required
Responsiveness that cannot be implemented in vue2
- arr[0] = newVal
- obj[newKey] = value
- delete
For these cases, vue2 is added byVue.$set
and rewrite array methods to implement. However, for vue3, becauseproxy
It is the proxy for the entire object, so it is born to support oneFeatures that cannot be supported, such as it can listen to add new attributes, and
Because the agent is every
key
So it doesn't know the new attributes. Such, the following lists some different responsive processing in vue3.
New attribute updates
Although proxy can listen to the addition of new attributes, the newly added attributes have not been collected through getters like existing attributes, which means that updates cannot be triggered. So our purpose is how to collect responses
First, let's take a look at how to deal with it in vue3for...in..
Looping, it can be known that the loop is used internally(obj
) to obtain keys that belong only to the object itself. So forfor..in
The interception of the loop is clear
const obj = {foo: 1} const ITERATE_KEY = symbol() const p = new Proxy(obj, { track(target, ITERATE_KEY) return (target) })
Here, we used onesymbol
data collected as a dependencykey
Because this is our interception operation in traversal is not associated with the specific key, but is an integral interception. When triggering a response, just trigger thissymbol
Collectedeffect
That's fine
trigger(target, isArray(target) ? 'length' : ITERATE_KEY) // Array situation tracking length
This will affect the length of the traversal object and will be introduced.ITERATE_KEY
Related side effect function execution
effect(() => { for(let i in obj) { (i) } })
After the side effect function is executed, we execute the rendering function similarly. Then go back to our new attributes,
= 1
Because the new attributes will be correctfor.. in ..
The cycle has an impact, so we need toITERATE_KEY
Take out the related side effect functions and re-execute them to see how this part is processed in the source code.
First of all, here issetter
Handling
function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { let oldValue = (target as any)[key] ... // Here is a sign of whether there is a key, that is, to determine whether it is a new element const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < : hasOwn(target, key) const result = (target, key, value, receiver) // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { // This shows that when new elements are added, trigger logic is taken trigger(target, , key, value) } else if (hasChanged(value, oldValue)) { trigger(target, , key, value, oldValue) } } return result } }
Then there is the specifictrigger
, get the corresponding logo to updateeffect
export function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { const depsMap = (target) if (!depsMap) { // never been tracked return } ... // also run for iteration key on ADD | DELETE | switch (type) { // This situation is the execution of trigger we just determined case : if (!isArray(target)) { // Get the ITERATE_KEY dependency collected ((ITERATE_KEY)) if (isMap(target)) { ((MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes (('length')) } break ... } for (const dep of deps) { if (dep) { (...dep) } } ... triggerEffects(createDep(effects)) ... } } function triggerEffect( effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || ) { if (__DEV__ && ) { (extend({ effect }, debuggerEventExtraInfo)) } if () { () } else { // Execute all effect functions () } } }
Summarize:
When traversing an object or array, use a unique identifiersymbol
Collect dependencies
- In fact, if you use obj directly in a template, it will be accompanied by a process and will also be accompanied by a collection of dependencies.
- If we do not use traversal objects in the js code, adding a single object will not trigger an update because there is no collection process.
Get the collected value when setting a new valuesymbol
Corresponding side effect function update
Processing of traversal array methods
When using arrays, it will be accompanied bythis
The problem of the proxy object cannot get the attributes, such as
const obj = {} const arr = reactive([obj]) ((obj) // false
This is becauseincludes
Internalthis
It points to the proxy object arr, and when comparing the elements, it is also the proxy object, so it is definitely not found by looking for the original object. Therefore, we need to modify the behavior of inlcudes.
new Proxy(obj, { get() { if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) { return (arrayInstrumentations, key, receiver) } } }) const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations() // Handle problems in centralized array traversal methods.function createArrayInstrumentations() { const instrumentations: Record<string, Function> = {} // instrument identity-sensitive Array methods to account for possible reactive // values ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => { instrumentations[key] = function (this: unknown[], ...args: unknown[]) { const arr = toRaw(this) as any for (let i = 0, l = ; i < l; i++) { track(arr, , i + '') } // we run the method using the original args first (which may be reactive) // Use the original parameters first, which may be the original object or the proxy object. const res = arr[key](...args) if (res === -1 || res === false) { // if that didn't work, run it again using raw values. // If not found, take the original parameters to compare and remove the responsive data. return arr[key](...(toRaw)) } else { return res } } })
How to change arrays
For array methods that may change the length of the original array,push
, pop
, shift
, unshift
, splice
It also needs to be processed, otherwise, it will fall into infinite recursion, considering the following scenario
cosnt arr = reactive([]) effect(() => { (1) }) effect(() => { (1) })
The execution process of these two is as follows:
- First, the first side effect function is executed, and then 1 is added to the array, and this process will affect the array
length
, so it will be withlength
Will betrack
, establish responsive contacts. - Then the second side effect function is executed,
push
This is because of the impactlength
,Firsttrack
Establish a responsive connection, and then try to take out the side effect function of length, which is the first side effect function to execute. However, before the second side effect function is completed, the first side effect function will be executed again. - The first side effect function is executed again and will also read
length
And setlength
, repeat the above collection and update process, and then execute the length collected from the second side effect - This cycle goes on and on. Eventually, the stack will overflow.
So the key to the problem lies inlength
Continuous reading and setting. So we need to read length to avoid making a connection between it and side effect functions
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => { instrumentations[key] = function (this: unknown[], ...args: unknown[]) { // Tracking changes is prohibited. pauseTracking() const res = (toRaw(this) as any)[key].apply(this, args) //Reply to track when the function is executed. resetTracking() return res } })
Summarize
vue2
andvue3
For newly added attributes, you need to obtain previously collected dependencies before you can distribute updates.vue2
Used inVue.$set
Relying on the existing dependencies of objects with newly added attributes, the update is distributed.vue3
It's becauseownKeys()
The collected before interceptsymbol
Dependency, triggers this when adding attributessymbol
Collected dependency updates.
For arrays,vue2
It is a method to intercept and modify the array, and then update the dependencies collected by the current array.()
,vue3
Because it has a certain ability to update array elements, but because it is becauselength
The stack overflow caused by this is therefore prohibited to track. At the same time, the access method also needs to be updated because the problem of inconsistency between the proxy object and the original object is solved by comparing the two when searching.
The above is a detailed explanation of the special processing of responsiveness in Vue3. For more information about responsiveness in Vue3, please pay attention to my other related articles!