The source code version of this article is Vue3.2.11. Here is the Vue2 responsive source code analysis point Analysis of Vue2 responsive principle source code
We know that compared with Vue2.x's responsive Vue3, the entire responsive Vue3 has been significantly upgraded; Then Vue3.2 has made many changes compared with version 3.0. Let's have a look
Difference between Vue3 and Vue2 responsive
Responsive performance improvement
According to the release of Youda on August 10 Vue3.2 original description hear:
- More efficient ref implementation, with a read increase of about 260% and a write increase of about 50%
- Increase collection speed by about 40%
- Reduce memory consumption by about 17%
Differences in use
In Vue2, as long as the properties written in the object returned by the data function in the component are automatically responsive
Vue3 defines common type responsive data through ref and complex type responsive data through reactive
<script setup> import { ref, reactive, toRefs } from "vue" const name = ref('Mu Hua') const obj = reactive({ name: 'Mu Hua' }) const data = { ...toRefs(obj) } </script>
Extension: you can turn a responsive object into a normal object through toRefs
When the reactive object defined by reactive is deconstructed (expanded) or destroyed, the response will become invalid. Because there are many attributes under the reactive instance, the deconstruction will be lost. Therefore, toRefs can be used when it is necessary to deconstruct and maintain the response
Differences in source directory structure
The core part of the Vue2 responsive source code is in the src/core/observer directory, but it also introduces many other directories, which are not independent and have a high degree of coupling
Vue3 responsive source code is all in the packages/reactivity directory. It does not involve anywhere else. It has independent functions, and is published as an npm package separately, which can be integrated into other frameworks
Principle difference
We know that using Object.defineProperty to implement responsive objects in Vue2 has some defects
- Based on attribute interception, all attributes will be recursive during initialization, which has a certain impact on performance. Moreover, the response cannot be triggered for new attributes added to the object. The corresponding solution is to add new attributes through the Vue.set() method
- The internal changes of the array cannot be detected. The corresponding solution is to rewrite 7 methods that will change the original array
In Vue3, Proxy is used for reconstruction, which completely replaces defineProperty, because Proxy can hijack the whole object, so the above problem does not exist
So how to solve these problems?
object
Let's take a look at the response processing of Vue2 when rendering for the first time. The source code address is line src/core/observer/index.js - 157
... Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { ... }, set: function reactiveSetter (newVal) { ... } }) ...
It can be seen from the parameters that it needs to find obj[key] in obj according to the specific key to intercept. Therefore, it is necessary to meet a precondition. You must know what the key is at the beginning, so you need to traverse each key and define getter s and setter s. This is also why the attributes added later have no response
This is the case in Vue3
// Ref source code ` packages / reactivity / ref.ts - line 142` // Reactive source code ` packages / reactivity / reactive.ts - line 173` new Proxy(target,{ // target is the object returned by the data of the component get(target, key){}, set(target, key, value){} })
It can also be seen from the parameters that when you start creating a response, you don't need to know what fields are in the object at all, because you don't need to pass a specific key, so even if it is added later, it can be intercepted naturally
That is to say, it will not recursively traverse, and set all the used and unused responses, so as to speed up the first rendering
array
In Vue2
- One reason is that the api Object.defineProperty cannot listen for changes in array length
- Second, because the array length may be very long, for example, lenth is thousands or tens of thousands, especially considering the performance consumption and user experience, Vue is designed not to listen to the operation of modifying array elements directly through subscripts
Extending a question, why can't you listen to the change of array length? Look at the picture first
As shown in the figure, the corresponding value can be changed only when configurable is true, which can also be understood as being monitored. Length itself cannot be monitored, so it cannot be monitored when the array length is changed
If you forcibly change its configurable to true, an error will be reported, because major browser manufacturers and JS engines do not allow you to modify the configurable of length, which is the case, so there is such code in the source code
// `Src / core / observer / index.js - line 144` const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return }
Therefore, in order to better operate the array and trigger the response, we rewrite the seven methods that will change the original array, and then manually send the update through ob.dep.notify(). The source address is src/core/observer/array.js
Proxy is used in Vue3. Proxy means proxy. Review the syntax
new Proxy(target,{ get(target, key){}, set(target, key, value){} })
according to Description of Proxy in MDN That's true
- Target: object virtualized by Proxy. It is often used as the storage back end of agents. Validate invariants (invariant semantics) about the object's non extensibility or non configurable attributes according to the target
Note: the length of the array is a non configurable property, so the Proxy can naturally monitor the length of the array
Differences in dependency collection
Vue2 implements dependency collection through three classes: Observer, Dep and Watcher. See my other article for the detailed process Analysis of Vue responsive principle source code
Vue3 collects dependencies through track and triggers updates through trigger. In essence, it is implemented with WeakMap, Map and Set. See the implementation process of the following source code for details
Defect difference
It is also mentioned above that defineProperty in Vue2 cannot monitor the internal changes of the new object property / array, and if the property value is an object, observe() will be called repeatedly for recursive traversal. In addition, it will set monitoring for all data properties, including unused properties, so the performance is naturally not so good
Vue3 mainly uses a lot of new features of Es6 +, which is not so compatible with old browsers
Vue3 responsive source code analysis
Let's take a look at several flag s defined in Vue3 to mark the type of target object. The following are the enumerated properties
Source address: packages / reactivity / reactive.ts - line 16
export const enum ReactiveFlags { SKIP = '__v_skip', IS_REACTIVE = '__v_isReactive', IS_READONLY = '__v_isReadonly', RAW = '__v_raw' } export interface Target { [ReactiveFlags.SKIP]?: boolean // Non responsive data [ReactiveFlags.IS_REACTIVE]?: boolean // Is the target responsive [ReactiveFlags.IS_READONLY]?: boolean // Is the target read-only [ReactiveFlags.RAW]?: any // Represents the source data corresponding to the proxy. This attribute is available when the target is already a proxy object }
Then start parsing one by one
reactive()
Source address: packages / reactivity / SRC / reactive.ts - line 87
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> export function reactive(target: object) { // If the target is a read-only object, it is returned directly if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { return target } return createReactiveObject( target, // You need to create a responsive target object data false, // Is not a read-only type mutableHandlers, mutableCollectionHandlers, reactiveMap // const reactiveMap = new WeakMap<Target, any>() ) }
The code here is very simple, mainly calling createReactiveObject()
createReactiveObject()
Source address: packages / reactivity / SRC / reactive.ts - line 173
function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>, proxyMap: WeakMap<Target, any> ) { // typeof is not of object type, and is returned directly if (!isObject(target)) { if (__DEV__) console.warn(`value cannot be made reactive: ${String(target)}`) return target } // If it is already responsive, it will be returned directly if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) { return target } // If it already exists in the map, it will be returned directly const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } // Return directly without responding const targetType = getTargetType(target) if (targetType === TargetType.INVALID) { return target } // Convert target to proxy const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) // Add to map proxyMap.set(target, proxy) return proxy }
After knowing about what needs to be done in this method, we need to understand the parameters passed in first
The parameter configuration definition is as follows
const get = /*#__PURE__*/ createGetter() const set = /*#__PURE__*/ createSetter() export const mutableHandlers: ProxyHandler<object> = { get, // get attribute set, // modify attribute deleteProperty, // Delete attribute has, // Do you have a property ownKeys // Collect keys, including symbol type or non enumerable keys }
Here, get, has and ownKeys will trigger the dependency collection track()
set and deleteProperty will trigger the update trigger()
Two important methods are createGetter and createSetter corresponding to get and set
createGetter()
Source address: packages / reactivity / SRC / basehandlers.ts - line 80
function createGetter(isReadonly = false, shallow = false) { return function get(target: Target, key: string | symbol, receiver: object) { // Access the corresponding tag bit if (key === ReactiveFlags.IS_REACTIVE) { return !isReadonly } else if (key === ReactiveFlags.IS_READONLY) { return isReadonly } else if ( // The receiver points to the caller. The judgment here is to ensure that the proxy itself rather than the successor of the proxy triggers the interception handle // There are two ways to trigger the block: one is to access the properties of the proxy object itself, and the other is to access the properties of the object with the proxy object on the object prototype chain, because the query will look down along the prototype chain key === ReactiveFlags.RAW && receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap ).get(target) ) { // Returns the target itself, which is the original value of the responsive object return target } // Is it an array const targetIsArray = isArray(target) // It is not a read-only type & & it is an array & & it triggers the methods in the arrayInstrumentations toolset if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) { // Through proxy call, this of arrayInstrumentations[key] must point to proxy return Reflect.get(arrayInstrumentations, key, receiver) } // proxy pre return value const res = Reflect.get(target, key, receiver) // key is symbol or accessed by__ proto__ Property does not do dependency collection and recursive response processing, but directly returns the result if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { return res } // Collect dependencies on target s that are not read-only. Because the read-only type will not change, the setter cannot be triggered, and the update will be triggered if (!isReadonly) { // Collect dependencies and store them in the corresponding global warehouse track(target, TrackOpTypes.GET, key) } // For shallow comparison, recursive conversion is not performed, that is, if the object has attribute values or objects, reactive() is not called recursively if (shallow) { return res } // The accessed property is already a ref object if (isRef(res)) { // Return ref.value except array const shouldUnwrap = !targetIsArray || !isIntegerKey(key) return shouldUnwrap ? res.value : res } // Because proxy can only proxy one layer, if the child element is an object, you need to continue the proxy recursively if (isObject(res)) { return isReadonly ? readonly(res) : reactive(res) } return res } }
track() relies on the collection to be put later, together with the distribution of updates
createSetter()
Source address: packages / reactivity / SRC / basehandlers.ts - line 80
function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { let oldValue = (target as any)[key] if (!shallow) { // Take the original value of the new value and the old value, because the newly transmitted value may be responsive data. It is meaningless to directly compare it with the original value on the target value = toRaw(value) oldValue = toRaw(oldValue) // Not array & & old value is ref & & new value is not ref, update ref.value to new value if (!isArray(target) && isRef(oldValue) && !isRef(value)) { oldValue.value = value return true } } else { // in shallow mode, objects are set as-is regardless of reactive or not } // Get key value const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key) // Assignment, equivalent to target[key] = value const result = Reflect.set(target, key, value, receiver) // Only when the receiver is a proxy instance, it sends updates to prevent the interceptor from triggering updates through the prototype chain if (target === toRaw(receiver)) { if (!hadKey) { // If the target does not have a key, it means a new key is added trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { // If the old and new values are not equal trigger(target, TriggerOpTypes.SET, key, value, oldValue) } } return result } }
trigger() sends updates later
There is a question: why use Reflect.get() and Reflect.set() instead of using target[key]?
according to MDN describes that set() returns a Boolean value For example, Reflect.set() will return a Boolean value indicating whether the modification was successful. It will directly assign target[key] = newValue, and an error will be reported if it does not return true. Moreover, no matter how the Proxy modifies the default behavior, you can obtain the default behavior through Reflect. get() similarly
Next is the core content related to the collection and distribution of updates. The relevant codes are all in the effect.ts file, which mainly deals with some side effects. The main contents are as follows:
- Create effect entry function
- track dependency collection
- trigger distribution update
- cleanupEffect clear effect
- Stop stop effect
- trackStack collects pausetracking, enabletracking, and resettracking of the stack
Let's start with the entry function
effect()
Source address: packages / reactivity / SRC / effect.ts - line 145
The main purpose here is to expose a method to create an effect
export function effect<T = any>( fn: () => T, options?: ReactiveEffectOptions ): ReactiveEffectRunner { // If it is already an effect function, take the original function directly if ((fn as ReactiveEffectRunner).effect) { fn = (fn as ReactiveEffectRunner).effect.fn } // Create effect const _effect = new ReactiveEffect(fn) if (options) { extend(_effect, options) if (options.scope) recordEffectScope(_effect, options.scope) } // If lazy is not true, execute effect directly once. The lazy of the calculated property is true if (!options || !options.lazy) { _effect.run() } // return const runner = _effect.run.bind(_effect) as ReactiveEffectRunner runner.effect = _effect return runner }
It can be seen that the main thing is to create a new ReactiveEffect in effect_ Effect instance, and the last runner method returned by the function points to the run method in ReactiveEffect
This shows that when the side effect function effect method is executed, the run method is actually executed
So we need to know what is done in the ReactiveEffect and the run method it returns
Let's keep watching
ReactiveEffect
Source address: packages / reactivity / SRC / effect.ts - line 53
The main thing to do here is to use the stack data structure effectstack to debug the execution of the effect before dependency collection, so as to ensure the highest priority of the current effect and clear the memory of the collected dependencies in time
It should be noted that the fn() function will be executed after the tag is completed. This fn function is the function closed by the side effect function. If it is in component rendering, fn is the component rendering function. When it is executed, it will access the data, trigger the getter of target[key], and then trigger the track for dependency collection, which is the dependency collection process of Vue3
// Temporary storage response function const effectStack: ReactiveEffect[] = [] // Dependency collection stack const trackStack: boolean[] = [] // Maximum nesting depth const maxMarkerBits = 30 export class ReactiveEffect<T = any> { active = true deps: Dep[] = [] computed?: boolean allowRecurse?: boolean onStop?: () => void // dev only onTrack?: (event: DebuggerEvent) => void // dev only onTrigger?: (event: DebuggerEvent) => void constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, scope?: EffectScope | null ) { // The related processing of effectScope is in another file, which is not expanded here recordEffectScope(this, scope) } run() { if (!this.active) { return this.fn() } // If there is no current effect in the stack if (!effectStack.includes(this)) { try { // activeEffect indicates the effect currently being processed by the dependency collection system // First, set the current effect as the globally activated effect, and collect the effects held by the activeEffect in the getter // Then put it on the stack effectStack.push((activeEffect = this)) // Resume dependency collection because dependency collection is suspended during the setup function itself enableTracking() // Record recursion depth bits trackOpBit = 1 << ++effectTrackDepth // If the number of nested layers of effect does not exceed 30, it is generally not exceeded if (effectTrackDepth <= maxMarkerBits) { // Marking dependencies is traversal_ deps attribute in the effect instance, and mark the w attribute of each dep as the value of trackOpBit initDepMarkers(this) } else { // The dependency related to the current effect will be cleared if the current effect is exceeded. Normally, it will not be cleared cleanupEffect(this) } // When executing the effect function, such as accessing target[key], the getter will be triggered return this.fn() } finally { if (effectTrackDepth <= maxMarkerBits) { // Complete dependency tag finalizeDepMarkers(this) } // Restore to previous level trackOpBit = 1 << --effectTrackDepth // Reset dependency collection status resetTracking() // Out of stack effectStack.pop() // Get stack length const n = effectStack.length // Point the current activeEffect to the last effect on the stack activeEffect = n > 0 ? effectStack[n - 1] : undefined } } } stop() { if (this.active) { cleanupEffect(this) if (this.onStop) { this.onStop() } this.active = false } } }
track()
Source address: packages / reactivity / SRC / effect.ts - line 188
track is a dependency collector, which is responsible for collecting dependencies and putting them into a dependency management center
// targetMap is the dependency management center, which is used to store the mapping relationship among responsive functions, target objects and keys // Equivalent to this // targetMap(weakmap)={ // target1(map):{ // key1(dep):[effect1,effect2] // key2(dep):[effect1,effect2] // } // } // Create a map for each target, and each key corresponds to a dep // dep is used to collect dependent functions, monitor the change of key value, and trigger the dependent functions in dep const targetMap = new WeakMap<any, KeyToDepMap>() export function isTracking() { return shouldTrack && activeEffect !== undefined } export function track(target: object, type: TrackOpTypes, key: unknown) { // If no effect is currently activated, no collection is required if (!isTracking()) { return } // Get target from dependency management center let depsMap = targetMap.get(target) if (!depsMap) { // If not, create one targetMap.set(target, (depsMap = new Map())) } // Get the dep set corresponding to the key let dep = depsMap.get(key) if (!dep) { // Create without depsMap.set(key, (dep = createDep())) } // Development environment and non development environment const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo) }
trackEffects()
Source address: packages / reactivity / SRC / effect.ts - line 212
Here, the currently active effect is collected into the corresponding effect set, that is, dep
Here's a look at two identifiers
dep.n: n is the abbreviation of newTracked, indicating whether it is the latest collection (whether it is the current layer)
dep.w: W is the abbreviation of wasTracked, indicating whether it has been collected to avoid repeated collection
export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false // If the number of nested layers of effect does not exceed 30, as mentioned above if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { // Mark new dependency dep.n |= trackOpBit // Dependencies that have been collected do not need to be collected repeatedly shouldTrack = !wasTracked(dep) } } else { // Switch to clear dependency mode when it exceeds shouldTrack = !dep.has(activeEffect!) } // If you can collect if (shouldTrack) { // Collect the currently active effect as a dependency dep.add(activeEffect!) // The currently active effect collects the dep collection activeEffect!.deps.push(dep) // Trigger onTrack event in development environment if (__DEV__ && activeEffect!.onTrack) { activeEffect!.onTrack( Object.assign( { effect: activeEffect! }, debuggerEventExtraInfo ) ) } } }
trigger()
Source address: packages / reactivity / SRC / effect.ts - line 243
Trigger is the trigger corresponding to the dependency collected by track, that is, it is responsible for obtaining the responsive function according to the mapping relationship, and then sending a notice to trigger effects to update
export function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { // Get dependencies from dependency management center const depsMap = targetMap.get(target) // Dependencies that have not been collected are returned directly if (!depsMap) { return } let deps: (Dep | undefined)[] = [] // The type passed in when trigger is triggered is clear type if (type === TriggerOpTypes.CLEAR) { // Add all associated dependencies to the queue and prepare to clear deps = [...depsMap.values()] } else if (key === 'length' && isArray(target)) { // If it is an array type and the length of the array changes depsMap.forEach((dep, key) => { // If the array length becomes shorter, you need to do the effects and trigger of the deleted array elements // That is, the effects corresponding to the elements with index number > = the latest len gt h of the array should be added to the queue to be cleared if (key === 'length' || key >= (newValue as number)) { deps.push(dep) } }) } else { // If the key is not undefined, add corresponding dependencies to the queue, such as adding, modifying, and deleting if (key !== void 0) { deps.push(depsMap.get(key)) } // Add, modify and delete are handled separately switch (type) { case TriggerOpTypes.ADD: // newly added ... break case TriggerOpTypes.DELETE: // delete ... break case TriggerOpTypes.SET: // modify ... break } } // Here you get targetMap[target][key] and coexist in deps // Next, take out the corresponding effect and call trigger effects to execute // Judge the development environment and pass in eventInfo const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } } else { const effects: ReactiveEffect[] = [] for (const dep of deps) { if (dep) { effects.push(...dep) } } if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } } }
triggerEffects()
Source address: packages / reactivity / SRC / effect.ts - line 330
Execute the effect function, that is, the update in distribute update
export function triggerEffects( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // Traverse the set function of the effect for (const effect of isArray(dep) ? dep : [...dep]) { /** Judge effect here== The reason for activeeffect is that it cannot be the same as the current effect For example: count.value + +, if this is an effect, the getter will be triggered, and track collects the currently active effect, Then count.value = count.value+1 will trigger the setter and execute the trigger, It will fall into an endless loop, so filter the current effect */ if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } // If the scheduler executes, the calculation attribute has a scheduler if (effect.scheduler) { effect.scheduler() } else { // Execute the effect function effect.run() } } } }
Create ref
Source address: packages/reactivity/src/ref.ts
The main purpose here is to deal with ref. first, let's take a look at some related functions, which will be used later
// Judge whether it is ref export function isRef(r: any): r is Ref { return Boolean(r && r.__v_isRef === true) } // Create ref function createRef(rawValue: unknown, shallow: boolean) { if (isRef(rawValue)) { // If it is already ref, it will be returned directly return rawValue } // Call RefImpl to create and return ref return new RefImpl(rawValue, shallow) } // Create a shallow ref export function shallowRef(value?: unknown) { return createRef(value, true) } // Uninstall a ref export function unref<T>(ref: T | Ref<T>): T { return isRef(ref) ? (ref.value as any) : ref }
RefImpl
Source address: packages / reactivity / SRC / ref.ts - line 87
From the above, we know that the ref object is created through new RefImpl(). The implementation of the RefImpl class is relatively simple. There is no more nonsense here. Please see the comments
class RefImpl<T> { private _value: T private _rawValue: T public dep?: Dep = undefined // There is one under each ref instance__ v_ Read only attribute of isref, identifying it as a Ref public readonly __v_isRef = true constructor(value: T, public readonly _shallow: boolean) { // Judge whether it is shallow comparison. If not, take the old value this._rawValue = _shallow ? value : toRaw(value) // Judge whether it is shallow comparison. If not, call convert. If it is an object, call reactive() this._value = _shallow ? value : convert(value) } // ref.value takes this value get value() { // Perform dependency collection trackRefValue(this) return this._value } set value(newVal) { // If it is a shallow comparison, take the new value instead of the old value newVal = this._shallow ? newVal : toRaw(newVal) // Compare old and new values if (hasChanged(newVal, this._rawValue)) { // Value has been changed, re assign this._rawValue = newVal this._value = this._shallow ? newVal : convert(newVal) // Distribute updates triggerRefValue(this, newVal) } } }
trackRefValue()
Source address: packages / reactivity / SRC / ref.ts - line 29
The main work here is to do some work on ref dependency collection. It is mainly to determine whether effect has been activated, whether there has been a collection of dependent effect, not to create a dep, to prepare for collection, and then to invoke trackEffects on this article for dependency collection.
export function trackRefValue(ref: RefBase<any>) { // If effect is activated, it is collected if (isTracking()) { ref = toRaw(ref) // If the attribute has not collected dependent functions, a dep is created to store dependent effect s if (!ref.dep) { ref.dep = createDep() } // development environment if (__DEV__) { trackEffects(ref.dep, { target: ref, type: TrackOpTypes.GET, key: 'value' }) } else { // Call the trackEffects collection dependency above this article trackEffects(ref.dep) } } }
triggerRefValue()
Source address: packages / reactivity / SRC / ref.ts - line 47
The source code of the ref distribution update here is relatively simple. It's nothing to say. Just distinguish the development environment, and then execute the trigger effects above to update the corresponding effect function
export function triggerRefValue(ref: RefBase<any>, newVal?: any) { ref = toRaw(ref) if (ref.dep) { if (__DEV__) { triggerEffects(ref.dep, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { triggerEffects(ref.dep) } } }
Here, the source code of Vue3's responsive object is basically analyzed
Previous highlights
- Performance optimization tips in 12 Vue development
- Analysis of Vue2 responsive principle source code
- Explain the virtual DOM and Diff algorithm in simple terms, and the differences between Vue2 and Vue3
- The 7 components of Vue3 communicate with the 12 components of Vue2, which is worth collecting
- What has been updated in the latest Vue3.2
- JavaScript advanced knowledge points
- Front end anomaly monitoring and disaster recovery
- 20 minutes to help you win HTTP and HTTPS and consolidate your HTTP knowledge system
epilogue
If this article is of little help to you, please give me a praise and support. Thank you