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({
timer: null.happy: 100.money: 0})...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
-
If the type is not object, Dev will warn you and return target. Reactive does not support reactive conversions of basic types.
-
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
-
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.
-
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
-
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
-
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
.set
Methods,CustomRefImpl
After 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
-
Reactive only responds to reference types;
-
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({
timer: null.happy: 100.money: 0})...return toRefs(people)
Copy the code
If you have any questions, please leave a comment and follow my blog
(after)