preface

This paper implements the responsive principle of VUe3.0, which does not involve template compilation and renderingCopy the code

Proxy

Proxy objects are used to define custom behavior for basic operations (such as property lookups, assignments, enumerations, function calls, and so on).

const proxy = new Proxy(target, handler);

The proxy and defineproperty

Object.defineproperty is a get and set listener for every attribute on an Object, so you need to loop over attributes at compile time, and you can’t listen for new attributes or for array changes.

Proxy is a syntax that appears in ES6. It can Proxy the whole object without polluting the original object. It can listen to the change of array and the addition and deletion of attributes, but there will be compatibility problems with browsers.

Reference vuE3 source code, vue3.0 responsive

Reactive, shallowReactive, readOnly, shallowReadonly method

Reactive: Listens on an object, and if the property is also an object, further listens.

ShallowReactive: Listens on an object, if the property is also an object, only on the first layer of the object.

ReadOnly: read-only and unmodifiable.

ShallowReadonly: Only the first layer is read-only.

Function reactive(target) {reactive method return createReact(target, false, reactiveHanlers); } function shallowReactive(target) {//shallowReactive return createReact(target, false, shallowReactiveHanlers); } function readOnly(target) {//readOnly method return createReact(target, true, readOnlyHanlers); } function shallowReadonly(target) {//shallowReadonly method return createReact(target, true, shallowReadonlyHanlers); }Copy the code

createReact

const reactiveMap = new WeakMap(); //reactive proxy cache const readonlyMap = new WeakMap(); Function createReact(target, isReadonly, baseHandlers) { If (typeof target == 'object' &&target! = null) { const proxyMap = isReadonly ? reactiveMap : reactiveMap; If (proxymap.has (target)) {return proxymap.get (target); if (proxymap.has (target)); } const proxy = new Proxy(target, baseHandlers); proxyMap.set(target, proxy); return proxy; }}Copy the code

Four proxy proxy objects

Const reactiveHanlers = {//reactive proxy configuration object get: createGetter(false, false), set: CreateSetter (false)} const shallowReactiveHanlers = {shallowReactive proxy configuration object get: createGetter(false, true), set: CreateSetter (true)} const readOnlyHanlers = {readOnly proxy configuration object get: CreateGetter (true, false), set() { Console. log(" read-only ")}} const shallowReadonlyHanlers = {shallowReadonly proxy configuration object get: CreateGetter (true, true), set() {console.log(" read-only ")}}Copy the code

createGetter

Create the get function in proxy

Arguments to createGetter: 1. Is it read-only? 2

Note: If the property is still an object, vue3 is optimized for performance so that deep proxying is done only when you use the object, not for long without recursive proxying

Function createGetter(isReadonly = false, shall = false) {return function (target, key, receiver) { const res = Reflect.get(target, key, receiver); if (! IsReadonly) {// If not read-only console.log(" collect dependencies "); } if (shall) {// if a shallow return res; } if (typeof target[key] == 'object' && target[key] ! = null) {return isReadonly? readOnly(res) : reactive(res); } return res; }}Copy the code

createSetter

Create the set function in proxy

CreateSetter (shall = false) {return function (target, key, newValue) {const oldValue = target[key]; Const result = reflect. set(target, key, newValue); Const hasKey = array.isarray (target)? Number(key) < target.length : key in target; if (! HasKey) {// add console.log(" trigger dependency, refresh attempt ")} else if (oldValue! = newValue) {// Modify console.log(" trigger dependency, refresh attempt ")} return result}}Copy the code

At this point we can test if our get and set functions fire

let obj={
    name:'aaaa'
}
let state=reactive(obj);

state.name

state.name='123';
Copy the code

Depend on the collection

effect

The effect method is mainly used to deal with the reactive form of functions, and can be used to calculate attributes and watchEffect and other functions. It can add itself to the DEPS of proxy by triggering the get method of proxy of reactive variables in the function to realize association with proxy, and other dependencies can also be collected into its OWN DEPS. Equivalent to Watcher in VUe2.0.

let uid = 0; // the id of each effect is equivalent to Watcher id in vue2.0. // Current effect, equivalent to dep. target const effectStack = []; //effect queue let targetMap = new WeakMap(); // Map function effect(fn, options = {}) {const effect = createReactEffect(fn, options = {}) { options); if (! options.lazy) { effect(); } return effect; } function createReactEffect(fn, options) {const effect = function reactiveEffect() {if (! Effectstack.push (effect); effectStack.push(effect); effectStack.push(effect); ActiveEffect = effect; // Update current activity effect fn(); } finally {effectstack.pop (); / / method performs, the stack activeEffect = effectStack [effectStack. Length - 1] | | undefined; }}} effect.id = uid++; // Add id effect._isEffect = true for each effect; // effect. Raw = fn; // current execution function effect.options = options; // The current configuration object return effect; }Copy the code

The dependency collection targetMap structure is described here:

TargetMap is a WeakMap structure, key is the target object, and value is a Map structure.

The Map key is the property of the current object, and the value is a Set.

The Set structure contains all effects for the current property. All effects are performed when the property value of this object changes.

Similar to vue2.0, each attribute has a dep. Inside the DEP is the associated Watcher.

Track

Depend on the collection

Function Track(target, type, key) {// ActiveEffect) {// There is no global effect return if the data is not used; } let depMap = targetMap.get(target); Map if (! DepMap) {// Create an empty Map, targetmap.set (target, depMap = new Map()); } let dep = depMap.get(key); // Get the dependent effect if (! Set depmap.set (key, dep = new Set()); } if (! dep.has(activeEffect)) { dep.add(activeEffect); // Add effect to set}}Copy the code

Modify createGetter

Function createGetter(isReadonly = false, shall = false) {return function (target, key, receiver) { const res = Reflect.get(target, key, receiver); if (! IsReadonly) {// If you are not read-only Track(target, 'get', key); / / collect rely on + + + + + + + + + + + + + + + + + + + +} the if (shall) {/ / if it is a shallow return res; } if (typeof target[key] == 'object' && target[key] ! = null) {return isReadonly? readOnly(res) : reactive(res); } return res; }}Copy the code

Here’s a test:

<div id="app"</div>
Copy the code
let obj={ name:'aaa' } let state=reactive(obj); effect(()=>{ document.getElementById("app").innerHTML=state.name; // Here we simulate data on view})Copy the code

Print the global targetMap

Triggered update

Trigger the update process

  1. If the global targetMap has no effect dependency on the current object, end and return.
  2. Checks whether the current object is an array and whether the key is the length of the array. If yes, effect must be executed if the key value in the array is greater than or equal to the array length.
  3. If it is an object, effect on the current object key is executed normally.
  4. If you are adding an empty key to an array, you need to effect the array length as well.
function trigger(target, type, key = undefined, newValue = undefined, oldValue = undefined) { let depsMap = targetMap.get(target); //map if (! DepsMap) {// If the global targetMap has no effect dependency on the current object, just end the return. return; } let effectSet = new Set(); Function add(effectsAdd) {if (effectsAdd) {effectsAdd. ForEach (item => { effectSet.add(item); }) } } // add(depsMap.get(key)); If (key == 'length' && array.isarray (target)) {depmap.foreach ((item, Index) = > {the if (index = = 'length' | | index > = newValue) {/ / if you modify the length or key greater than or equal to the length of the array of the add (item); } }) } else { if (key ! = undefined) {// add(depmap.get (key)); } switch (type) { case 'add': If (array.isarray (target)) {// Array is added, Add (depmap.get ('length')); } break; }}} effectset.foreach (item => {// Last loop executes all effect items (); })}Copy the code

Modify the createSetter

CreateSetter (shall = false) {return function (target, key, newValue) {const oldValue = target[key]; Const result = reflect. set(target, key, newValue); Const hasKey = array.isarray (target)? Number(key) < target.length : key in target; if (! HasKey) {// Add trigger(target, 'add', key, newValue); / / the new type of add++ + + + + + + + + + + + + + +} else if (oldValue! = newValue) {// Modify trigger(target, 'set', key, newValue, oldValue); / / modify the type of the set + + + + + + + + + + + + + + + + +} return result}}Copy the code

Here’s a quick test:

< div id = "app" > < / div > < button onclick = "change ()" > change < / button >Copy the code
let obj={ name:'aaa' } let state=reactive(obj); effect(()=>{ document.getElementById("app").innerHTML=state.name; }) function change(){state.name='123456'; }Copy the code

See page refresh

Click on the

Ref

Reactive is proxying our objects and ref is responding to our basic data types. Internally, we use Object. DefineProperty in vue2.0.

Drawname =ref(drawname, 1);Copy the code

We actually operate on the name.value property when we use it

Function ref(target) {//ref constructor return createRef(target, false); } function createRef(target, shallow = false) {return new RefImpl(target, shallow); } class RefImpl { constructor(target, shallow) { this._v_isRef = true; // ref this. Shallow = shallow; // This._value = target; } get value() {this._value Track(this, 'get', 'value'); Return this._value; } set value(newValue) { if (newValue ! = this._value) { let oldValue = this._value; this._value = newValue; trigger(this, 'set', 'value', newValue, oldValue); }}}Copy the code

toRef

When we structure the properties of an object that has been treated in a reactive way, are the values generated by the structure still reactive?

let {name}=reactive({name:'zxl'});
Copy the code

Vue3.0 does not allow this operation and requires toRef

Function toRef(target, key) {return new ObjectRefImpl(target, key); } class ObjectRefImpl { constructor(target, key) { this.key = key; this.target = target; } get value() { return this.target[this.key]; } set value(newValue) { if (newValue ! = this.target[this.key]) { this.target[this.key] = newValue; }}}Copy the code

toRefs

        function toRefs(target) {
            let ref = Array.isArray(target) ? new Array(target.length) : {};
            for (let key in target) {
                ref[key] = toRef(target, key);
            }
            return ref;
        }
Copy the code