preface
We all know that Vue’s responsiveness is implemented through data hijacking, Vue2’s data hijacking is implemented through Object.defineProperty, and part of the Vue3 upgrade was to replace defineProperty with a more powerful Proxy. As we all know, Proxy is the upgraded version of defineProperty, and the reason why it is not promoted is the problem of suitability. Now the time is finally ripe 🎉.
What we’re looking at now is reactive and REF, which are used in different ways. When we use it, we may wonder: since both are reactive, what is the essential difference between the two? With this question we will analyze the source code of both.
Reactive source code
Without further ado, let’s look at reactive’s source code:
//packages/reactivity/src/reactive.ts
export function reactive(target: object) {
// If the data passed is readonly, the readonly data is returned directly
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
Return a Reactive object
return createReactiveObject(
target,
false,
mutableHandlers, // Proxy handler for responsive data, general Object and Array
mutableCollectionHandlers // The proxy handler of the responsive Set is generally Set, Map, WeakMap, WeakSet)}Copy the code
The code above is very simple when we call Reactive:
- First check whether the current data is a readonly object. In this case, readonly is the object created by using [ue. Readonly]
- The createReactiveObject() method is then called to create the reactive object
As the code above mentions a ReactiveFlags enumeration, let’s interrupt the type of the parameter
export const enum ReactiveFlags {
SKIP = '__v_skip'.// Used to indicate that the object cannot be proxied
IS_REACTIVE = '__v_isReactive'.//reactive
IS_READONLY = '__v_isReadonly'.//readonly
RAW = '__v_raw' // is the original target on proxy
}
Copy the code
Then we use createReactiveObject to create reactive objects, so before we look at createReactiveObject, we need to know,
- The types of reactive mappings in Vue3 are:
// The original object is mapped to the responsive object
export const reactiveMap = new WeakMap<Target, any>()
//readonly Mapping from object to original object
export const readonlyMap = new WeakMap<Target, any>()
Copy the code
Reactive. Ts stores two WeakMaps: reactiveMap and readonlyMap. These represent the original object to reactive object mapping and the ReadOnly object to reactive object mapping respectively.
- In addition, there is the data type of Target:
const enum TargetType {
INVALID = 0.// Other types
COMMON = 1.//Array, Object
COLLECTION = 2 //Set, Map, WeakMap, WeakSet
}
Copy the code
COMMON represents the basic types of Array and Object; COLLECTION represents Set, Map, WeakMap, WeakSet; INVALID represents INVALID type
**createReactiveObject()*
First, the createReactiveObject receives a target object with four parameters, whether it is readOnly, a basic data type hijacking handler, and a collection type data hijacking handler.
function createReactiveObject(
target:Target,
isReadonly:boolean,
baseHandlers:ProxyHandler<any>,
collectionHandlers:ProxyHandler<any>
)
Copy the code
Next, judge the target data. If it is not an object type, the data is returned directly and an alert is given in the development environment. If the target data is returned directly by Proxy, the exception is when we call readOnly on a Reactive basis.
// If not an object is returned directly, the development environment will give a hint
if(! isObject(target)) {if (__DEV__) {
console.warn(`value cannot be made reactive: The ${String(target)}`)}return target
}
if( target[ReactiveFlags.RAW] && ! (isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) {return target
}
// If it is already a reactive Proxy, return the reactive Proxy directly
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
Copy the code
When we call readonly on reactive, it sounds a bit tricky, so let’s go straight to the test case and see when this happens:
//packages/reactivity/__tests__/readonly.spec.ts
test('readonly + reactive should make get() value also readonly + reactive'.() = > {
const map = reactive(new Collection())
const roMap = readonly(map)
const key = {}
map.set(key, {})
const item = map.get(key)
expect(isReactive(item)).toBe(true)
expect(isReadonly(item)).toBe(false)
const roItem = roMap.get(key)
expect(isReactive(roItem)).toBe(true)
expect(isReadonly(roItem)).toBe(true)})Copy the code
And finally, it’s up to us reactive to create the reactive core logic. Use Proxy to implement data hijacking, passing in our hijacking handler. Proxy reactive data is also collected to update reactive data maps.
// Only common types and collection types can be processed as responsive
/ / main types: Object, Array, Map, Set, WeakMap, WeakSet
const targetType = getTargetType(target)
// If the data type is invalid
if (targetType === TargetType.INVALID) {
return target
}
// Create a reactive Proxy
const proxy = new Proxy(
target,
WeakMap, WeakSet, Common: Object, Array); // Pass handler according to type (Collection: Set, Map, WeakMap, WeakSet, Common: Object, Array)
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// The reactive data is collected and the reactive data map is updated
proxyMap.set(target, proxy)
return proxy
Copy the code
After looking at the above code, let’s briefly summarize crateReactiveObject:
- First determine whether the data we pass is of object type, if not directly return the data.
- Check whether it is already a Proxy. If so, return the data directly. One exception to this rule is that it does not apply when working with reactive data using vue.readonly ().
- Finally, we use Proxy to complete the mapping of our data hijacking and responsive data, provided that our data types are satisfied.
Ref source
We have seen the source code of Reactive and know how to create reactive. Now let’s take a look at the source code implementation of REF. We believe that the essential difference between REF and Reactive will be solved when we see the end.
Reactive: Ref creates a ref object through createRef. CreateRef calls new RefImpl() to create a ref object.
//packages/reactivity/src/ref.tss
export function ref(value? : unknown) {
return createRef(value)
}
// Create the ref object
function createRef(rawValue: unknown, shallow = false) {
// If it is a ref, return the value directly
if (isRef(rawValue)) {
// If the target data is of type ref, return it directly
return rawValue
}
return new RefImpl(rawValue, shallow)
}
Copy the code
RefImpl class RefImpl class
class RefImpl<T> {
// Current data
private _value: T
// This attribute marks whether the object is ref
public readonly __v_isRef = true
constructor(private _rawValue: T, public readonly _shallow = false) {
Value = value (shallow is false); otherwise, convert rawValue to reactive
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
// Start track when reading value
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
// Check whether the new and old values are the same. If they are different, then assign a value and trigger
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
Copy the code
Here, we begin by analyzing its constructor:
- The RefImpl takes two parameters, the first being our raw data and the second being the shallow identifier
- Determine whether we call the convert method to reactive based on our shallow
Then there are the get and set methods for value in RefImpl, which are very simple:
- The track method is fired when the GET method is fired
- When the set method is triggered, it must first determine whether newValue and oldValue are the same, then assign the value and trigger the trigger method
As the above mentioned trigger and track, we can refer to the previous article as follows: From here we can also see that the REF object mounts the responsive data to the value attribute
Let’s take a look at the convert implementation:
// Data type conversion
const convert = <T extends unknown>(val: T): T => // Reactive ()? reactive(val) : valCopy the code
The implementation of this method is also very simple. It simply checks whether our data is Object or not, and reactive processes our data directly if it is, otherwise it directly returns the data.
Reactive: Ref (reactive) : ref (reactive) : ref (reactive) : ref (reactive)
- The underlying reactive implementation is the same in both cases, which are data reactive implemented through createReactiveObject
- The difference is that ref is created with a **__v_isRef** flag added to indicate that the ref object is currently a REF object
- Ref adds data hijacking to its value attribute. Track method is triggered when get and trigger method is triggered when set
Here we post part of the ref test case: this test case also confirms the set part of our source code that the same data assignment does not trigger assignment and trigger
describe('reactivity/ref'.() = > {
it('should hold a value'.() = > {
const a = ref(1)
expect(a.value).toBe(1)
a.value = 2
expect(a.value).toBe(2)
})
it('should be reactive'.() = > {
const a = ref(1)
let dummy
let calls = 0
effect(() = > {
calls++
dummy = a.value
})
expect(calls).toBe(1)
expect(dummy).toBe(1)
a.value = 2
expect(calls).toBe(2)
expect(dummy).toBe(2)
// same value should not trigger
a.value = 2
expect(calls).toBe(2)
expect(dummy).toBe(2)})Copy the code
conclusion
This section is straightforward. It looks at a source code implementation of Reactive and REF. In fact, even the source code of Reactive and REF is only the core, and some derivative methods, such as Readonly, shallowReactive, isReactive, etc. None of them are covered here because they are derived from them, so there is no need to explain them in detail.
Because recently move brick more busy, so the article always intermittently write, also do not have too strong coherence 😭. Hope the back will be better, also be to oneself good expectation 🥳.