• Vue source code analysis and practice
  • Author: JW_LINNN

preface

It seems that in a live broadcast, some students asked especially big, if you want to read the source code, now is to watch Vue2 or Vue3? Uvu said Vue3 because the project structure was clearer after its refactoring. Therefore, it is more recommended to read the source of Vue3.

Take a look at the Vue3 code structure and use monorepo to manage the code by splitting the different modules into packages directories, which contain extremely important responsive modules (@vue/reactivity) that can also be introduced separately from vUE in the project. As we all know, Vue3 responsiveness changed from defineProperty of Vue2 to Proxy. As the core of the Composition API, it’s worth taking a look at the internal implementation.

@vue/reactivity common API (from official website 🤪)

  • reactive: Returns a reactive copy of the object.
  • ref: Receives an internal value and returns a responsive variablerefObject.refObject has a single point to an internal valueproperty.
  • isProxy: Checks whether the object is configured byreactivereadonlyTo create theproxy.
  • isReactive; Check whether the object is configured byreactiveCreate a responsive proxy.
  • isRef: Checks whether there is one valuerefObject.
  • computed: Accept onegetterFunction, and according togetterReturns an immutable responserefObject, or, accepts an object withgetsetFunction to create a writable ref object.
  • watch:watchAPI and optional APIthis.$watch(and the correspondingwatchOption) is exactly equivalent.watchYou need to listen to a specific data source and perform side effects in a separate callback function. By default, it is also lazy — that is, the callback is invoked only when the listening source changes.
  • effect: side effect function, passed in and executed for dependency collection.

Lead to learn

Proxy

Proxy objects are used to create a Proxy for an object to intercept and customize basic operations (such as property lookup, assignment, enumeration, function calls, and so on).

parameter

  • targetIn order to useProxyThe wrapped target object (which can be any type of object, including a native array, a function, or even another proxy).
  • handlerAn object, usually with functions as attributes, that define the behavior of the proxy object when performing various operations. The following is a list of common oneshandlerObject properties

let target = {
    num: 0
};

/** Create a proxy object */
const proxy = new Proxy(target, {
    /** * Trigger when setting a property * target target object * property name of the property being set * Value Value being set * Receiver Proxy or inheriting Proxy object */
    set (target, property, value, receiver) {},

    /** * When a property is acquired, it triggers * target target object * property Name of the acquired property * Receiver Proxy or inheriting Proxy object */
    get (target, property, receiver) {},

    has (target, property) {}, // The catcher for the in operator

    deleteProperty (target, property) {}, // The trap for the delete operator

    ownKeys (target) {} / / Object. GetOwnPropertyNames method, Object. GetOwnPropertySymbols method, for, in the Object. The keys () the trap
});

Copy the code

Let’s look at a simple chestnut

Suppose you have a scenario where you click a button to synchronize the current time, and the number of clicks is recorded. If you are in Vue, two-way data binding can be implemented very quickly. What do you do if you’re using native Javascript?

<span id="text"></span>
<button id="click-button">CLICK ME</button>
Copy the code
let obj = {
    time: new Date(),
    clickNum: 0
}

let dom = document.getElementById('text');
dom.innerHTML = `Time: ${obj.time}, Click: ${obj.clickNum}`;

let clickBtn = document.getElementById('click-button');
clickBtn.addEventListener('click'.() = > {
    obj.time = new Date(a); obj.clickNum +=1;

    // At this point, the HTML does not update the OBj data in real time based on the click event if the value is not reassigned
    // So we need to add one more step
    dom.innerHTML = `Time: ${obj.time}, Click: ${obj.clickNum}`;
});
Copy the code

What about @vue/reactivity?

const {reactive, effect} = window.VueReactivity;

// Define a responsive object
const obj = reactive({
    time: new Date(),
    clickNum: 0
})

// Side effect functions that need to be executed when responsive object data changes
effect(() = > {
    let dom = document.getElementById('text');
    dom.innerHTML = `Time: ${obj.time}, Click: ${obj.clickNum}`
})

let clickBtn = document.getElementById('click-button');
clickBtn.addEventListener('click'.() = > {
    // Click the button to trigger a responsive data change
    // Because reactive data is used, there is no need to pay attention to updates elsewhere after data changes
    obj.time = new Date(a); obj.clickNum +=1;
})
Copy the code

Implement a minimally responsive model

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

The simplest way to do this is by collecting dependencies and notifying you of changes

The implementation process is similar to the above figure (except for the left part of updating and rendering the virtual DOM). Reactive objects are created first, and are intercepted during GET and SET operations. Dependencies are collected during GET operations and updated during set operations. With the Effect side effect function, you pass in a reactive callback that is touched when the reactive data changes. The specific code is as follows:


const reactiveMap = new WeakMap(a);/** * create a responsive object */
function reactive (target) {
    const existingProxy = reactiveMap.get(target);
    if (existingProxy) {
        return existingProxy;
    }
    const proxy = new Proxy(target, {
        get: (target, key, receiver) = > {
            const res = Reflect.get(target, key, receiver);
            track(target, "get", key);
            return res;
        },
        set: (target, key, value, receiver) = > {
            let oldValue = target[key];
            const res = Reflect.set(target, key, value, receiver);
            trigger(target, "set", key, value, oldValue);
            returnres; }}); reactiveMap.set(target, proxy);return proxy
}

Copy the code

/** targetMap similar to this result targetMap = {[key: Object]: {[key: string]: Set
      
       }} */
      
const targetMap = new WeakMap(a);let activeEffect;

/** * is used to trace dependencies */
function track (target, type, key) {
    if (activeEffect === undefined) return;
    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()))
    }
    dep.add(activeEffect);
}


Copy the code

/** targetMap similar to this result targetMap = {[key: Object]: {[key: string]: Set
      
       }} */
      
const targetMap = new WeakMap(a);/** * triggers dependency updates */
function trigger (target, type, key, newValue, oldValue, oldTarget) {
    const depsMap = targetMap.get(target);
    if(! depsMap) {return;
    }
    let deps = depsMap.get(key);
    deps.forEach(effect= > effect.run())
}
Copy the code
let activeEffect;

class ReactiveEffect {
    constructor(fn) {
        this.fn = fn;
    }
    run () {
        / / activate activeEffect
        activeEffect = this;

        // Track will be triggered for dependency collection
        Activeeffects need to be saved for dependency collection
        this.fn();

        // Reset activeEffect to undefined after dependency collection
        activeEffect = undefined; }}/** * Side effect function */
function effect (fn) {
    const _effect = new ReactiveEffect(fn);
    _effect.run();
    const runner = _effect.run.bind(_effect);
    runner.effect = _effect;
    return runner;
}
Copy the code

This greatly simplifies the @vue/ ReActivity code, just a minimal model based on the example above. @Vue/ReActivity also provides other apis, such as REF, computed, shallowReactive(creating a responsive proxy, It tracks the responsiveness of its own property, but does not perform deep reactive transformations of nested objects (exposing raw values). Looking at the full version through the simplified version above should be a little clearer.

Hope that through this article, I suggest you read the complete source code to continue to learn. For example, when we talk about proxy, we usually just proxy the Object (literal meaning Object) and deal with its GET and set operations. But have you ever thought about how the operation of proxy is for arrays? There are so many common methods for arrays. This is not like the get and set operations on simple objects. What about the corresponding responses? Read the full source code and you’ll find out, as I will after reading it anyway

The resources

View the source of this article

View examples online

Proxy

@vue/reactivity