preface

We’ve been working on some componsition-API for a long time, so if you want to see the source code, start with reactive.

Personal Habits:

  • Look at the code with the problem, not blindly
  • The problem is to look at the source of the main line, to develop around the main line, the process and the main line does not have much to do with the ignored ignored
  • Open source projects are generally better encapsulated, it is possible that a function will refer to multiple files in the function, each time jump to the jump directory will be recorded, to avoid jumping to do not know where to jump
  • When you go into a file, put the functions away so you can see them

Lead to

  • Clone vue-Next from Github.

  • Use yarn install to install dependencies.

  • Add sourcemap to dev script in package.json and the final command is: “dev”: “node scripts/dev.js –sourcemap”.

  • Because Vue3 is packaged through Rollup, rollup also needs to be installed.

  • Running NPM run dev generates a dist folder in the package/vue/ directory.

  • Create the init. HTML file in package/vue/examples/.

    <! DOCTYPE html> <html lang="en"> <body> <div id="app"> <h1>hello</h1> </div> <script src=".. /dist/vue.global.js"></script> <script> const { watch, watchEffect, createApp, reactive } = Vue debugger const data = reactive({ a: 1, b: 2, count: 0 }) </script> </body> </html>Copy the code
  • Run the file as a service and enter the debug. Break points to the reactive function.

Reactive data

The reactive function initially decides to return target if the target passed in is a read-only object.

The createReactiveObject function is called. Let’s take a look at the parameters:

  1. targetThe object to be proxied
  2. Whether the read-only
  3. baseHandlerscollectionHandlersAre allproxyhandler, the corresponding arguments aremutableHandlersmutableCollectionHandlers, according to thetargetType to determineproxyhandler
  4. reactiveMapIs a constant declared in the current file to store dependencies, now it only needs to be aweakMapType data is fine

The function starts with several cases of target and returns for each case.

The main concern is this piece of code

const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
Copy the code

The handler is selected based on the type of target. TargetType is the value returned by calling getTargetType. When the targetType is Map/Set/WeakMap/WeakSet, collectionHandlers are passed to the Proxy; otherwise, baseHandlers are used.

baseHandlers

baseHandlersThe correspondingmutableHandlers

get

getThe correspondingcreateGetterThe return value of the

CreateGetter takes two arguments, isReadonly and shallow, and returns a different value for each of the three special cases in the get method. Among several judgment in the use of shallowReadonlyMap/readonlyMap/shallowReactiveMap their type and type are the same weakMap reactiveMap, just for different condition.

Below figure out whether the array, if is an array, and isReadonly to false and the key is the includes/indexOf/lastIndexOf/push/pop/shift/unshift/splice one of these words is executed

return Reflect.get(arrayInstrumentations, key, receiver)
Copy the code

That is to rewrite the methods mentioned above. This code is for the edge case

The includes/indexOf lastIndexOf is in order to avoid to produce the following situation

Const obj = {} const arr = reactive([obj]) arr.indexOf(objCopy the code

Push/pop/shift/unshift/splice is the way to avoid these change the array length, in some cases, into the infinite loop

Then we get the value of the key in the target

const res = Reflect.get(target, key, receiver)
Copy the code

Then judge the case where key is symbol. If isReadonly is false, the track function is called for dependency collection.

Let’s go back to track, let’s go back to track after we’ve seen get.

It then determines if the shallow passed in to createGetter is true and if res is ref.

Call the reactive function recursively if res is an object and isReadonly is false, and call the readonly function recursively if res is an object.

It can be seen from the call to reactive function that the difference between vuE3 and VUE2 is not only in defineProperty and proxy, but also in the timing of processing reactive function. DefineProperty makes all attributes on the target responsive at first, but vue3 converts the value of the key to responsive when you read the key.

Next, look at the track function, which is used to collect dependencies.

It starts with a judgment, and the next piece of code is to get the data for the current key and construct a data structure

let depsMap = targetMap.get(target) if (! depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (! dep) { depsMap.set(key, (dep = new Set())) }Copy the code

The constructed data structure looks like this

targetMap = { target: { key: [ dep, ... ] // Dep, set type} // Map type} // weakMap typeCopy the code

The activeEffect is later added to the dep. The activeEffect here is really the dependency we want to collect.

Later, dep was added to the activeEffect deps. This piece was not understood at that time. Later, it was understood by looking up the data. If the development environment also calls the onTrack function, this function is documented to debug the listener’s behavior.

At this point, the track function ends.

Problems encountered

  • What is Activeeffect.deps.push (DEP) and why is it done

    The dep of the current key is pushed semantically into the activeEffect deps, but why this is done remains to be seen. Checked on the Internet, this article is still very good, to this problem point also has a certain analysis.

    Summary in this article: This operation is in preparation for the cleanup method mentioned in the reactiveEffect method, which clears the dePs in the activeEffect before collecting the dependencies. The purpose of clearing and collecting is to avoid a dependency collection that is triggered only under certain conditions in the activeEffect. It has been collected before, but does not need to be collected this time, so it will be cleared first and then collected for the current dependency collection. Ensure that the currently collected dependencies are those that need to be collected. In addition to the reactiveEffect method, this method will be used in the watchEffect, which is not covered in this article, so I will not expand it.

    set

setThe correspondingcreateSetterThe return value of the

The createSetter takes a parameter shallow to indicate whether the object is shallow, and returns the set function directly.

If it is not a shallow object, oldValue is of type REF, but newValue is not of type REF, because the data of type REF is already reactive, so there is no need to trigger the dependency again through the trigger function.

Reactive, reactive, reactive, reactive, reactive, reactive, reactive, reactive, reactive, reactive, reactive, reactive, reactive, reactive, reactive

Reflect.set() adds value to target and then determines whether the key is in target. If the key is not in the target, invoke the trigger function with the ADD type to trigger the dependency, and invoke the trigger function with the SET type otherwise. Return the return value of reflect.set ().

Next, look at the trigger function

Getdependency () getdependency () getdependency () getdependency () getdependency () getdependency ();

The effects and Add methods of type set are defined. The add method basically iterates through the incoming dePs and adds each effect to the effects, so that all effects to be executed are stored in the Effects.

The next step is to execute the corresponding add function based on the type passed in.

Next, we define the run function, which is responsible for executing effect. If effect has an onTrigger method in the development environment, we will execute the onTrigger method first. This method is the same as the onTrack function called in the track function. Used to debug the listener’s behavior. If the effect has a scheduler in it, select the scheduler to execute the effect. Otherwise, execute the effect directly.

So now that we’ve defined the run function, we’re going to go through the effects, pass in the run function and execute it.

That’s all there is to the trigger function.

deleteProperty

This method doesn’t have much to say, so use reflect.deleteProperty () to do the deletion, determine that the key is the target’s own and call trigger to trigger the dependency, Then return the return value of reflect.deleteProperty ().

has

Execute reflect.has (), call track to collect dependencies when the key is of non-symbol type, and return the return value of reflect.has ().

ownKeys

Call track to collect dependencies and return reflect.ownKeys (target).

collectionHandler

collectionHandlersThe correspondingmutableCollectionHandlers

get

getThe correspondingcreateInstrumentationGetterThe return value of the

Defines the isReadonly createInstrumentationGetter function and shallow two parameters, at the time of call incoming here are all false.

The handlers are retrieved from the handlers. The handlers are retrieved from the handlers. The handlers are retrieved from the handlers, and the handlers are retrieved from the handlers.

Finally figure out whether the key instrumentations own property, if is it Reflect the get () to the first value is instrumentations, whereas directly to the target, Finally, return the return value of reflect.get ().

So, the collectionHandlers, they’re not going to be deployed, but they’re going to be based around our theme.

The collectionHandlers end here.

conclusion

  1. The responsive approach of VUe3 passes the target object to the proxy, and then uses handlers to collect dependencies when getting values such as get/has/ownKeys and triggering dependencies when changing values such as set/deleteProperty.

  2. When the value of a key of the target is still an object, VUE3 will process the value responsively only when the key is read, while VUE2 will directly process all attributes and attribute values responsively when it is initialized.

    The above is my reading process about reactive source code. When I finally read it and ran the demo, I found that when I entered the track function, it was returned from the first judgment place, without real collection dependence. Which brings me to my other question: when do you collect dependencies? With this problem, you can continue to look at the source code.

Refer to the link

  • Source series: Vue3 simple (a)
  • Vue3 document
  • Vue3’s most verbose Reactivity data response rationale