We know that Vue 2.0 uses ojbect.defineProperty to hijack the reading and modification of existing attribute values of objects, but the API cannot listen for new and deleted attributes of objects, and in order to deeply hijack internal attributes of objects, Internal attributes must be recursively called ojbect.defineProperty at initialization time, which creates a performance cost. To address these issues, Vue 3.0 uses proxies to rewrite reactive logic and optimize performance.

Use case

Let’s take a look at Vue 3.0’s ringing API as an example:

ChangePerson can change the value of the responsive data Person, which triggers the component to re-render and update the DOM.

As you can see in Vue 3.0, developers use reactive functions to determine which data is reactive, thereby avoiding unnecessary reactive performance costs. For example, in this case we don’t need nowIndex to be responsive data. (Of course, Vue 2.0 can also define data outside of the data function, which is also non-responsive.)

Let’s look at how reactive functions work!

reactiveApi-related processes

reactive

Code description:

  1. If the targettargetisreadonlyObject, returns the target object directly becausereadonlyObject cannot be set as a responsive object
  2. callcreateReactiveObjectThe function continues the process.

createReactiveObjectCreate reactive objects

Code description:

  1. If the target object is not data or an object, the object is returned directly, giving an error warning in the development environment.
  2. iftargetIs already aProxyObject is returned directlytarget, (target['__v_raw']Very cleverly designed: iftargetisProxyObject,target['__v_raw']The triggergetMethod in the cache objectreactiveMapTo find whethertargetThe object’sProxyIs the object equal totargetItself). An exception is handled if it is executed for a reactive objectreadonlyThe function needs to continue.
  3. inreactiveMapTo check whether there is already a correspondingProxyObject directly returns the correspondingProxyObject.
  4. Ensure that only specific data can become reactive, otherwise return directlytarget. The responsive whitelist is as follows:
    • targetIt has never been executedmarkRawMethod, ortargetObjects have no__v_skipProperty value or__v_skipThe value of the property isfalse;
    • targetCannot be an unextensible object, that istargetIt has never been executedpreventExtensions.sealandfreezeThese methods;
    • targetforObjectorArray;
    • targetforMap.Set.WeakMap.WeakSet;
  5. Through the use ofProxyFunction hijackedtargetObject, and the result is a responsive object. The handler here will be based ontargetThe object is different (both functions are passed as arguments) :
    • ObjectorArrayThe handler of delta is deltacollectionHandlers;
    • Map.Set.WeakMap.WeakSetThe handler of delta is deltabaseHandlers;
  6. Store reactive objectsreactiveMapCache it in,keyistarget.valueisproxy.

mutableHandlersThe processing function

We know that accessing an object property triggers get, setting an object property triggers set, deleting an object property triggers deleteProperty, the IN operator triggers HAS, and getOwnPropertyNames triggers ownKeys. Let’s look at the code logic of these functions.

getfunction

Since no arguments are passed, isReadonly and shallow are both false by default.

Code logic:

  1. If you get__v_isReactiveProperty, returnstrueSaid,targetIt’s already a responsive object;
  2. To obtain__v_isReadonlyProperty, returnsfalse; (readonlyIs another reactive API, not explained yet.)
  3. To obtain__v_rawProperty, returnstargetIn itself, this property is used to determinetargetWhether it is already a responsive object;
  4. If target is an array and hits some properties, for exampleincludes.indexOf.lastIndexOfAnd so on, these function methods of the array are executed, and executed on each element of the arrayCollect rely ontrack(arr, TrackOpTypes.GET, i + '')And then throughReflectGets the value of the array function;
  5. ReflectEvaluation;
  6. Check if it is a special attribute value:symbol.__proto__.__v_isRef.__isVueIf it’s just return what we got beforeres, do not do follow-up processing;
  7. Perform collection dependencies;
  8. If it isrefIf thetargetNot arrays orkeyIf it’s not an integer, perform data unpacking, which involves another reactive APIref, do not explain;
  9. ifresIs an object, recursively executedreactive,resBecome a responsive object. Here is an optimization trick, only the property value is accessed before it is hijacked, avoiding the performance cost of initialization and full hijacking.

getWhen a function is called

To answer this question, we need to go back to our previous article on setup – demystifying the SETUP functions of Vue3.0.

The setup() function is executed in the setupStatefulComponent function and results:

The logic for handleSetupResult is to assign the interval setupResult to instance.setupState:

This instance.setupState is proxied by instance. CTX, so accessing and modifying instance. CTX can directly access and modify instance.setupState:

We mentioned earlier that render subtree VNode is a call to the render function. Let’s template it to see what the render function looks like in our example.

Clearly, when rendering the template, the Person property object is fetched from CTX, which is the Person property object of setupState. When the name,age, and address of the Person property object of setupState are fetched, the get function will be invoked to obtain the corresponding value.

Summary: When a component instance executes the render function to generate a subtree VNode, the get function of the reactive object is called.

trackCollect rely on

We mentioned collecting dependencies twice in the code explanation of the get function above. What are collecting dependencies? To achieve responsiveness, when the data changes will automatically implement some functions, such as the execution of some functions. Because the side effects render function can trigger a re-rendering of the component to update the DOM, the dependencies collected here are the side effects render functions that need to be performed when the data changes.

That is, when the GET function is executed, the side effect rendering function of the corresponding component is collected.

We can use our example to illustrate the final result:

setfunction

Code logic:

  1. If the value has not changed, return directly;
  2. throughReflectSet a new value;
  3. Not a property on the prototype chain, if a new property is implementedaddThe type oftriggerIf it is modified property executionsetThe type oftrigger. If reflect.set the property on the prototype chain calls the setter again, so trigger is not executed twice.

triggerDistribution of depend on

Trigger code has clear logic, which is to find the corresponding function from the dependency targetMap collected from the get function, and then execute these side effects rendering functions to update the DOM.

getAssociated with the side effect rendering function

Let’s go back and answer one more question: how is the side effect rendering function collected from the get function determined, i.e. which side effect rendering function to associate when accessing Person. name?

Let’s go through the logic step by step:

  • Component mountedmountComponentThe last step isExecute render function with side effects:

  • setupRenderEffectSo let’s define onecomponentUpdateFnComponent rendering function, and then thiscomponentUpdateFnEncapsulated in theReactiveEffectAnd will beReactiveEffectThe object’srunMethod assigned to the component objectupdateProperty, and then executeupdateMethod, in fact, is executionReactiveEffectThe object’srunMethods.

  • ReactiveEffectThe run method holds the function passed in. The current scenario iscomponentUpdateFnComponent rendering function, and utilizes two global variableseffectStackandactiveEffect.

ComponentUpdateFn is assigned to activeEffect and pushed into the effectStack before executing the Run method. When executed, componentUpdateFn is removed from the stack, and activeEffect is assigned to the new function at the top of the stack.

  • componentUpdateFnCalled at execution timerenderComponentRoot, which essentially executes the component instance objectrenderMethods.

  • So that’s it for this article,renderMethod if the corresponding type of data is accessedgetThe function,getIs collected in

Here, a stack structure is designed to solve the problem of effect nesting.

Side effects render function execution filter

If you think about it a little bit, right? Name,age, and address are all changed, and then they are all associated with the same render function. Theoretically, changing all three values at the same time will trigger three component rerenders, which is obviously not reasonable. How does Vue control execution only once?

  • We need to go back againReactiveEffectencapsulationcomponentUpdateFnTo render the function, let’s first look at the second parameterscheduler:

  • Distribute dependencies if anyschedulerWill performscheduler:

  • queueJobThe execution logic is to filter out the task if it is in the queue.

At the end

This paper introduces the principle of Vue3.0 in detail: Using Proxy to hijack objects, the access to the object will trigger the GET method, at this time will be dependent collection; The set method is triggered when the object data is modified, and a dependency is issued that calls the component’s side effect rendering function (but not limited to) so that the component can be re-rendered and the DOM updated.

This article introduced the reactive principle, and I’ll look at the implementation logic of some commonly used reactive apis (such as Readonly, REF, and so on).