In a nutshell, vue3 response is: 1. Effect collects effect for all attributes. 2. When the value of this property changes, effect is re-executed. Here is a simple vuE3 response by hand, in-depth understanding of it.

1. ReactiveApi implementation

Package/reactivity/SRC/index export response of various methods

export {
    reactive,
    shallowReactive,
    shallowReadonly,
    readonly
} from './reactive'

export {
    effect
} from './effect'


export {
    ref,
    shallowRef,
    toRef,
    toRefs
} from './ref'
Copy the code

Package/reactivity/SRC/index/reactivity function currie

Vue3 uses proxy to realize data proxy. The core is to intercept get method and set method, collect effect function when obtaining value, and trigger corresponding effect re-execution when modifying value

import { isObject } from "@vue/shared"
import {
    mutableHandlers,
    shallowReactiveHandlers,
    readonlyHandlers,
    shallowReadonlyHandlers
} from './baseHandlers'
export function reactive(target){
    return createReactiveObject(target,false,mutableHandlers)
}
export function shallowReactive(target){
    return createReactiveObject(target,false,shallowReactiveHandlers)
}

export function readonly(target){
    return createReactiveObject(target,true,readonlyHandlers)
}

export function shallowReadonly(target){
    return createReactiveObject(target,true,shallowReadonlyHandlers)
}

const reactiveMap = new WeakMap(a);const readonlyMap = new WeakMap(a);// Core method createReactiveObject
// Reactive the API intercepts only object types
WeakMap will cache the proxy object and the corresponding proxy result, if it has been proxy, directly return it.
/ / reactiveMap readonlyMap corresponding response/read-only mapping table
export function createReactiveObject(target,isReadonly,baseHandlers){
    if( !isObject(target)){
        return target;
    }
    const proxyMap = isReadonly? readonlyMap:reactiveMap
    const existProxy = proxyMap.get(target);
    if(existProxy){
        return existProxy;
    }
    const proxy = new Proxy(target,baseHandlers);

    proxyMap.set(target,proxy); 

    return proxy;
}
Copy the code

package/reactivity/src/index/baseHandlers

Reactive, shallowReactive, readonly, shallowReadonly handler Set and get – > createGetter/createSetter get collected rely on set trigger updates, distinguish is new, modified.

// Implement new Proxy(target, handler)

import { extend, hasChanged, hasOwn, isArray, isIntegerKey, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOrTypes } from "./operators";
import { reactive, readonly } from "./reactive";


const get = createGetter();
const shallowGet = createGetter(false.true);
const readonlyGet = createGetter(true);
const shallowReadonlyGet = createGetter(true.true);
const set = createSetter();
const shallowSet = createSetter(true);

export const mutableHandlers = {
    get,
    set
}
export const shallowReactiveHandlers = {
    get: shallowGet,
    set: shallowSet
}

let readonlyObj = {
    set: (target, key) = > {
        console.warn(`set on key ${key} falied`)}}export const readonlyHandlers = extend({
    get: readonlyGet,
}, readonlyObj)
export const shallowReadonlyHandlers = extend({
    get: shallowReadonlyGet,
}, readonlyObj)

function createGetter(isReadonly = false, shallow = false) { // Intercept the fetch function
    return function get(target, key, receiver) {
    const res = Reflect.get(target, key, receiver); // Equivalent to target[key];
        if(! isReadonly){// Collect dependencies and update corresponding views after data changes
            track(target,TrackOpTypes.GET,key)
        }
        if(shallow){
            return res;
        }
        if(isObject(res)){
            // vue2 is recursive at first, vue3 is proxying when it is evaluated. Vue3's proxy mode is lazy proxy
            return isReadonly ? readonly(res) : reactive(res)
        }
        returnres; }}function createSetter(shallow = false) { // Block setup
    return function set(target, key, value, receiver) {

        const oldValue = target[key]; // Get the old value
        
       // Whether there is an attribute key in target
        let hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target,key);
        // The key is the index, and the index is within the length of the array
        // Check if there is a key in the current target

        const result = Reflect.set(target, key, value, receiver); // Target [key] = value


        if(! hadKey){/ / new
            trigger(target,TriggerOrTypes.ADD,key,value);
        }else if(hasChanged(oldValue,value)){
            // Modify and old value! = = new values
            trigger(target,TriggerOrTypes.SET,key,value,oldValue)
        }
        // Notify effect of the corresponding property to re-execute when data is updated


        returnresult; }}Copy the code

2. The effect

package/reactivity/src/index/effect

By default, the global variable activeEffect is executed once to save the current effect. The effectStack is used to record the current activeEffect

export function effect(fn, options: any = {}) {
    const effect = createReactiveEffect(fn, options);
    if(! options.lazy) { effect(); }return effect;
}
let uid = 0;
let activeEffect; // Store the current effect, which is currently running
const effectStack = []
function createReactiveEffect(fn, options) {
    const effect = function reactiveEffect() {
        if(! effectStack.includes(effect)) {// Ensure that effect is not added to the effectStack
            try {
                effectStack.push(effect);
                activeEffect = effect;
                return fn(); // The get method is used when the function is executed
            } finally {
                effectStack.pop();
                activeEffect = effectStack[effectStack.length - 1];
            }
        }
    }
    effect.id = uid++; // Make an effect flag to distinguish effect
    effect._isEffect = true; // This is a reactive effect
    effect.raw = fn; // leave the original function corresponding to effect
    effect.options = options; // Save user properties on effect
    return effect;
}
Copy the code

3. Track relies on collection

package/reactivity/src/index/effect

The difference is that watcher. Update of vue2 executes effect for any property change and does not re-execute effect for any unused data change

WeakMap Key: {age:12} value:(map) =>{age => set(effect)}



const targetMap = new WeakMap(a);// Collect the current effect function for an attribute in an object
export function track(target, type, key) {
    if (activeEffect === undefined) {
        This property does not collect dependencies because it is not used in effect
        return;
    }
    let depsMap = targetMap.get(target);
    if(! depsMap) {// If there is no targetmap.get (target)
        targetMap.set(target, (depsMap = new Map));
    }
    let dep = depsMap.get(key);
    if(! dep) {//如果没有depsMap.get(key)
        depsMap.set(key, (dep = new Set))}if(! dep.has(activeEffect)) {// If there is no dep.has(activeEffect)dep.add(activeEffect); }}Copy the code

4. Trigger the update

The set method is triggered, and the trigger method is triggered to update the effect corresponding to the key

  • Let’s see if it’s modifiedArray lengthBecause changing the length has a bigger impact

If the length of the change is smaller than the collected index, then this index also needs to trigger effect re-execution

  • It could be objects

  • What if I change an index in the array?

If an index is added, a length update is triggered

package/reactivity/src/index/effect

import { isArray, isIntegerKey } from "@vue/shared";
import { TriggerOrTypes } from "./operators";

// Find the effect of the property to execute (array, object)
export function trigger(target, type, key? , newValue? , oldValue?) {

    // If this attribute does not collect effects, no action is required
    const depsMap = targetMap.get(target);
    if(! depsMap)return;

    const effects = new Set(a);// The effect is changed
    const add = (effectsToAdd) = > {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect= >effects.add(effect)); }}// I want to store all the effects to be executed in a new collection, and finally execute them together

    // 1. Check whether the array length is changed
    if (key === 'length' && isArray(target)) {
        // If the corresponding length has a dependency collection needs to be updated
        depsMap.forEach((dep, key) = > {
            if (key === 'length' || key > newValue) { // If the length of the change is smaller than the collected index, then this index also needs to trigger effect re-execution
                add(dep)
            }
        })
    } else {
        // Can be an object
        if(key ! = =undefined) { // It must be modified, not added
            add(depsMap.get(key)); // If it is a new one
        }
        // What if I change an index in the array?
        switch (type) {  // If an index is added, the length is updated
            case TriggerOrTypes.ADD:
                if (isArray(target) && isIntegerKey(key)) {
                    add(depsMap.get('length'));
                }
        }
    }
    effects.forEach((effect: any) = > effect())
}


Copy the code

5. Ref

A REF is essentially implemented through a class’s attribute accessor that wraps a common value type. Ref converts an ordinary type to an object with a value attribute that points to the original value.

import { hasChanged, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOpTypes } from "./operations";
import { reactive } from "./reactive";

export function ref(value) { // ref Api
    return createRef(value);
}

export function shallowRef(value) { // shallowRef Api
    return createRef(value, true);
}
function createRef(rawValue, shallow = false) {
    return new RefImpl(rawValue, shallow)
}

const convert = (val) = > isObject(val) ? reactive(val) : val; // Recursive response

class RefImpl {
    public _value; // a _value attribute is declared but no value is assigned
    public __v_isRef = true; // The generated instance is appended with a __v_isRef attribute to indicate that it is a ref attribute
    constructor(public rawValue, public shallow) { // Add a modifier to indicate that the property is placed on the instance
        this._value = shallow ? rawValue : convert(rawValue)// If it is deep, make everything inside responsive
    }
    // Property accessors for the class
    get value() { // Proxy value will help us proxy to _value
        track(this, TrackOpTypes.GET, 'value');
        return this._value
    }
    set value(newValue) {
        if (hasChanged(newValue, this.rawValue)) { // Check whether the old and new values change
            this.rawValue = newValue; // The new value will be used as the old value
            this._value = this.shallow ? newValue : convert(newValue);
            trigger(this, TriggerOrTypes.SET, 'value', newValue); }}}Copy the code

6. ToRefs implementation

To convert an attribute in an object to a ref attribute, toRefs is based on ref, traversing the object plus ref

class ObjectRefImpl {
    public __v_isRef = true;
    constructor(public target, public key) {}
    get value() {/ / agent
        return this.target[this.key] If the original object is reactive, the collection will be relied on
    }
    set value(newValue) {this.target[this.key] = newValue; // Update is triggered if the original object is reactive}}// Convert the value of a key to ref
export function toRef(target, key) { // We can convert the value of an object to type ref
    return new ObjectRefImpl(target, key)
}


export function toRefs(object){ // Object may pass an array or an object
    const ret = isArray(object) ? new Array(object.length) :{}
    for(let key in object){
        ret[key] = toRef(object,key);
    }
    return ret;
}
Copy the code

7. Differences between VUe2 and VUe3

Vue2 recurses the data in data from the beginning, while VUe3 proxies the data when it is evaluated. Vue3’s proxy mode is lazy proxy. Having an attribute in an object collect its current effect function is equivalent to the Watcher in VUe2. Ref uses defineProperty internally and Reactive uses proxy internally.

For arrays, VUe2 uses the rewrite array method. Vue3 is the trigger method when it triggers an update. If an index is added, the length of the index will be updated. If the length of the array changed is smaller than that of the collected index, the index will also need to trigger effect re-execution.