When vuE3 was officially released, many of you couldn’t wait to try out the new Composition API. While familiarizing yourself with the new Composition API, we found that the official documentation provides two apis for setting responsive data: Ref () and reactive() : ref() and reactive() are used for basic types of reactive processing, and reactive() is used for reference types. Today let’s look at the source code and see if we can derive a relatively simple and practical use


After experiencing VUe3 for a period of time, when reading official documents, some new API instructions were passed by, leading to a variety of usages and some questions, such as:

  • Can a REF generate a reactive copy of a primitive type as well as define a reactive copy of a reference type
  • Does Reactive only accept objects? What’s wrong with accepting other types of values
  • When do you use REF or Reactive
  • Value is used for values wrapped by ref and value wrapped by reactive is used for values wrapped by reactive
  • Deconstruction breaks reactive tracing

Problems, etc.

A, test,

Testing a
import { reactive, ref } from "vue";

let test = reactive('zhangsan');
let testObj = reactive({
    a: 1
})

setTimeout(() = > {
    test = 'lisi'
    testObj.a = 5
    console.log('======>test', test)
    console.log('======>testobj', testObj)
}, 2000)
Copy the code

TestObj is a Proxy object. TestObj is a Proxy object. TestObj is a Proxy object. TestObj is a Proxy object

Test two
import { reactive, ref } from "vue";

let test = ref('zahngsan');
let testObj = ref({
    a: 1
})

setTimeout(() = > {
    test.value = 'lisi'
    testObj.value.a = 5
    console.log('======>test', test)
    console.log('======>testobj', testObj)
}, 2000)
Copy the code

With ref both test and testObj become responsive, the page references are updated, but they need to be modified with.value, printing two variables that return the RefImpl object

Test three
// The Composition API logic is removed from usegrowup.js
import { ref, onMounted, onUnmounted } from "vue";

export default function useGrowUp({ 
    const timer = ref(null);
    const happy = ref(100);
    const money = ref(0);

    function growUp({
        money.value++;
        happy.value--;
    };
    
    onMounted(() = > {
        timer.value = setInterval(growUp, 5000); 
    })
    
    onUnmounted(() = > {
        clearInterval(timer.value);
    })

    return { happy,money };
}
Copy the code

You can use it in a Vue file like this

<template>
    <h2>The SIMS</h2>
    <div>Happy: {{happy}}, wealth: {{money}}</div>
</template>
<script>
    import useGrowUp from './useGrowUp.js'
    setup() {
        const { money, happy } = useGrowUp();
        console.log('money', money)
        console.log('happy', happy)

        return {
            money,
            happy
        }
    }
</script>
Copy the code

You can see that money and Happy are RefImpl objects, and the phenomenon is responsive; But if you don’t want to use a lot of refs (), see what happens if you use a reactive() to return people

const people = reactive({
    timernull.happy100.money0})...return people;
Copy the code

Const {money, happy} = useGrowUp(); const {money, happy} = useGrowUp();

What is done in the source code to address these phenomena

Two, source code analysis

1, the reactive
/ / source location packages/reactivity/SRC/reactive. Ts
export function reactive(target: object) {
    // if trying to observe a readonly proxy, return the readonly version.
    if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
        return target
    }

    return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers,
        reactiveMap
    )
}

// ReactiveFlags enumeration This enumeration type plays a big role in the subsequent source code
export const enum ReactiveFlags {
    SKIP = '__v_skip'.// Flags whether to skip the reactive
    IS_REACTIVE = '__v_isReactive'.// Flag whether a reactive object is available
    IS_READONLY = '__v_isReadonly'.// Whether the tag is a read-only object
    RAW = '__v_raw' // marks whether the value is original
}
Copy the code

Check whether it is read-only, if yes, directly return target, then execute createReactiveObject operation, the code of createReactiveObject

// Reactive core creates logic
function createReactiveObject(
    target: Target,
    isReadonly: boolean,
    baseHandlers: ProxyHandler<any>,
    collectionHandlers: ProxyHandler<any>,
    proxyMap: WeakMap<Target, any>
) {
    if(! isObject(target)) {if (__DEV__) {
            console.warn(`value cannot be made reactive: The ${String(target)}`)}return target
    }
    // target is already a Proxy, return it.
    // exception: calling readonly() on a reactive object
    if( target[ReactiveFlags.RAW] && ! (isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) {return target
    }

    // target already has corresponding Proxy
    const existingProxy = proxyMap.get(target)
    if (existingProxy) {
        return existingProxy
    }
    // only a whitelist of value types can be observed.
    const targetType = getTargetType(target)
    if (targetType === TargetType.INVALID) {
        return target
    }
    const proxy = new Proxy(
        target,
        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
    )
    proxyMap.set(target, proxy)
    return proxy
}
Copy the code
  1. If the type is not object, Dev will warn you and return target. Reactive does not support reactive conversions of basic types.

  2. If the target has a raw value tag, it is already responsive and returns target directly, with the exception that you can readonly the proxy object.

The following situations will be allowed

let obj = {
    a: 1
}
let testReactive = reactive(obj)
let testReadonly = readonly(testReactive)

console.log('======>reactive', testReactive)
console.log('======>readonly', testReadonly)
Copy the code

  1. Check whether target already has a mapping of proxy object. If so, it can be directly taken out and returned. As will be seen in the following code, a target and proxy mapping will be added to the generated proxy object in the WeakMap structure.

  2. GetTargetType get the type of target, only the type in the whitelist can be white response, see the code know that is to support Object, Array, Map, Set, WeakMap, WeakSet;

const enum TargetType {
    INVALID = 0,
    COMMON = 1,
    COLLECTION = 2
}

function targetTypeMap(rawType: string) {
    switch (rawType) {
        case 'Object':
        case 'Array':
            return TargetType.COMMON
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
            return TargetType.COLLECTION
        default:
            return TargetType.INVALID
    }
}

function getTargetType(value: Target) {
    return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
        ? TargetType.INVALID
        : targetTypeMap(toRawType(value))
}
Copy the code
  1. The second parameter of the Proxy constructor is handler. Different handlers are used for different types. CollectionHandlers are used for collection types such as Map, Set, WeakMap and WeakSet. Object and Array use baseHandlers, so we use baseHandlers in most of our daily development. The whole responsive system also has collecting dependencies and triggering dependencies, and these operations are carried out in handlers. We can write a separate article about handler and it’s not the focus of this article

  2. Finally, a target and proxy mapping will be added to WeakMap structure.

The above shows only the process of Reactive. There are other apis like shallowReactive and ReadOnly, which call createReactiveObject.

2, ref
// Show only the code needed in the source code
export function ref<T extends object> (value: T) :ToRef<T>
export function ref<T> (value: T) :Ref<UnwrapRef<T>>
export function ref<T = any> () :Ref<T | undefined>
export function ref(value? : unknown) {
    return createRef(value, false} // createref
function createRef(rawValue: unknown, shallow: boolean) {
    if (isRef(rawValue)) {
        return rawValue
    }
    return new RefImpl(rawValue, shallow} // According to__v_isRefDetermine whether or notref
export function isRef<T> (r: Ref<T> | unknown) :r is Ref<T>
export function isRef(r: any) :r is Ref {
    return Boolean(r && r.__v_isRef === true)}Copy the code

Ref defines this as an overloaded version of TS, which allows a function to take different numbers or types of arguments and do different things.

This is very simple, check to see if the value passed in has a __v_isRef flag, if it does, it is already reactive, and return the value directly, without creating an instance of the RefImpl class, then look at the RefImpl class

class RefImpl<T> { private _value: T private _rawValue: T public dep? : Dep =undefined
    public readonly __v_isRef = true
    
    constructor(value: T, public readonly _shallow: boolean) {
        this._rawValue = _shallow ? value : toRaw(value)
        this._value = _shallow ? value : convert(value)
    }

    get value() {
        trackRefValue(this)
        return this._value
    }

    set value(newVal) {
        newVal = this._shallow ? newVal : toRaw(newVal)
        if (hasChanged(newVal, this._rawValue)) {
            this._rawValue = newVal
            this._value = this._shallow ? newVal : convert(newVal)
            triggerRefValue(this, newVal)
        }
    }
}
Copy the code

As can be seen from RefImpl class, the value passed in by ref will be obtained and modified by a value. This is because JS itself does not have the ability to monitor the basic type, so it is necessary to put the value on a carrier and perform get and set operations on it before collecting dependency (track). Trigger dependencies.

_shallow determines whether the response for the current value is created by reactive()

The internal value _rawValue is stored and if _shallow is false equals value directly, otherwise the value is toRaw to the raw or primitive value of the proxy object

If _shallow is false, the value is created by calling reactive(). If _shallow is false, the value is created by calling reactive()

In the same way, get and set methods are added to compare the old and new values and collect the dependency and trigger the dependency process

const convert = <T extends unknown>(val: T): T =>
    isObject(val) ? reactive(val) : val
Copy the code

It’s worth mentioning that REF also supports customizationget.setMethods,CustomRefImplAfter looking at the source code,The official caseThat makes sense

class CustomRefImpl<T> { public dep? : Dep =undefined
    private readonly _get: ReturnType<CustomRefFactory<T>>['get']
    private readonly _set: ReturnType<CustomRefFactory<T>>['set']
    public readonly __v_isRef = true
    
    constructor(factory: CustomRefFactory<T>) {
        const { get, set } = factory(
            () = > trackRefValue(this),
            () = > triggerRefValue(this))this._get = get
        this._set = set
    }

    get value() {
        return this._get()
    }

    set value(newVal) {
        this._set(newVal)
    }
}
Copy the code

Conclusions and suggestions

So far, this paper has analyzed the main execution process of two important APIref and Reactive in VUe3.0, as can be seen

  1. Reactive only responds to reference types;

  2. The ref can be responsive to any type, but the problem is obvious. Value. This problem is solved in the latest proposal (RFC), which uses a new syntactic sugar, as follows:

ref:count = 1
count++
Copy the code

It’s still a debate in the community, but in the end whatever way it comes out, this.value should work out;

Usage suggestions:

1. Many times you can use a REF to create any responsive data;

If there are many basic types that need to be defined, you can wrap them into an object and use Reactive once

3. To solve the problem that reactive proxy objects generated by Reactive will lose responsiveness when they are deconstructed, toRefs can be used to convert reactive objects into refs one by one. In test 3, change them to the following

const people = reactive({
    timernull.happy100.money0})...return toRefs(people)
Copy the code

If you have any questions, please leave a comment and follow my blog

(after)