This package is inline to the Global&Browser ESM version of the user-facing renderer (for example, @vue/runtime-dom), but can also be distributed as a standalone package. Standalone versions should not be used with pre-bundled versions of user-facing renderers because they will have different internal storage for reactive wiring. User-facing renderers should re-export all apis in this package.
For the full exposed API, see SRC/index.ts. You can also run YARN Build reactivity –types from the buyback root, which generates an API report at temp/reactivity.api.md.
In human terms: this package will be embedded in the vue renderer (@vue/runtime-dom). However, it can also be distributed separately and referenced by third parties (independent of vUE). However, if your renderer is exposed to frame users, it may already have a built-in response mechanism, which is completely different from our reactivity and not necessarily compatible (I’m looking at you, React-dom). For the API, just look at the source code or types.
The key files we need to pay attention to from index are REF, Reactive, computed and Effect
Refs: extends, info in ts in large numbers
Extends: T extends U? X: Y can be expressed in plain English as: if T contains a ‘subset’ of the types U contains, then the result X is taken, otherwise the result Y is taken
Infer: In the extends statement, the infer keyword is also supported to infer a type variable and efficiently match patterns of types. However, this type variable can only be used in branches of true. export type UnwrapRef = T extends Ref ? UnwrapRefSimple : UnwrapRefSimple in plain English is infer X means to declare a variable that can then be used. And if you look back at the extends of the longest part of the file, you can infer that a lot of boundary values have been done to create/read a ref. The specific details of each will not be detailed ~ because the initial version is not so detailed, it is estimated that the community needs more and more to be compatible
The obJ generated by createRef does not have any proxy-related operations. From the previous information we know that Reactive builds reactive data, but the parameters must be objects. But when ref’s input parameter is an object, reactive also needs to do the transformation. So what is the purpose of ref?
For primitive data types, the reference to the original data is lost when the function is passed or the object is deconstructed. In other words, we cannot make the primitive data type, or the deconstructed variable (if its value is also a primitive data type), responsive data.
In human terms, proxies do not use primitive types, but can only hijack reference types
For example: We can never make basic data like A or X responsive, and proxies can’t hijack basic data.
const a = 1;
const { x: 1 } = { x: 1 }
Copy the code
Refs are built to work on Reactive data, but refs are exported to reactive data. Refs are built to work on reactive data
Don’t worry about looking at track and trigger. Continue to look at Reactive first
Reactive: External reference
The factory function handles the processing of the data broker uniformly
The utility function def does not know what it is
The conclusion mentioned above is verified, and ref will be introduced here
The above three paragraphs are the core code of the file. It is not difficult to understand that WeakMap here is used to store responsive data
The key is the factory functions that are introduced externally, the implementation of baseHandlers and collectionHandlers, and why are they distinguished this way?
BaseHandles: If you look at external references, you probably know what they are except for the Effect file
And then see
It is found that they are only used for filtering in GET, builtinSymbols is a native action for filtering objects, and the other action is internal to vue
There’s a lot more to this boundary value processing and it shouldn’t be the logical point
Go directly to the mutableHandlers that Reactive introduces
You can see that he is a ProxyHandler
So here we can found that triggers the response of the key lies in the five functions in the get, set, deleteProperty, has, ownKeys
Get: created by createGetter (Of course createGetter can create three other types of GET, but this is not important for reactive logic. This article only deals with normal cases.)
Look at the code:
As you can see, we’re doing separate returns for arrays, objects, and so on and the key thing here is we’re doing a track call in the middle
Object will continue to be proxied in response to the underlying value, that is, if the key is an Object, the underlying value will only be proxied when it is read
So if we target[key] and Reflect that’s why we’re not going to get the res directly,
Here’s a chestnut I found online:
let user = {
_name: "Guest".get name() {
return this._name; }};let userProxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(target) // user object {_name: "Guest"}
returntarget[prop]; }});let admin = {
__proto__: userProxy,
_name: "Admin"
};
console.log(admin.name); // Guest
Copy the code
When we read admin.name, since the admin object has no properties of its own, the search will go to its prototype. 3. When name reads a property from the proxy, its GET triggers and returns the property from the original object, running its code this=target in the context. Therefore, the result this._name comes from the original object target, which is: from user.
What if we use Reflect
let user = {
_name: "Guest".get name() {
return this._name; }};let userProxy = new Proxy(user, {
get(target, prop, receiver) { // receiver = admin
return Reflect.get(target, prop, receiver); }});let admin = {
__proto__: userProxy,
_name: "Admin"
};
console.log(admin.name); // Admin
Copy the code
The receiver parameter in reflect. get preserves a reference to the correct reference to this (i.e. admin), which passes the correct object usage in reflect. get to GET.
At this time, except track didn’t look carefully, everything else was roughly understood
Look at the set:
Shallow (1)
If the old value is ref data, but the new value is not, update the value attribute of the old value, and return the update success
The following paragraph:
In the case of arrays, it’s easy to see if the key is larger than the length of the array
But when we call the original function of the array, such as push
const proxy = new Proxy([] and {set(target, key, value, receiver) {
console.log(key, value, target[key])
return Reflect.set(target, key, value, receiver)
}
})
proxy.push(1)
// 0 1 undefined
// length 1 1
Copy the code
The internal logic is to assign a value to the subscript and then set length, which is equivalent to triggering the set twice. There is a haschanged, and the second change in length does not trigger the trigger
However, using unshift does trigger n times
const proxy = new Proxy([1.2.1.3.1.1] and {set(target, key, value, receiver) {
console.log(key, value, target[key])
return Reflect.set(target, key, value, receiver)
}
})
proxy.unshift(1)
Copy the code
Although the actual logic does require this, it is obvious that the subscript trigger will not be used if there are not so many subscript values, so how will the effect produced by this trigger be handled? It is probably a reactiveEffect that will be consumed or not produced at all
CollectionHandlers I feel are special processing of data types that are not commonly used
Effect: should be the core of the whole reactive style
If the effect is not in the stack, it will be pushed into the stack. This is to prevent the two effect loops from triggering each other’s reactive value changes
Const foo = reactive({value1:1, value2:0}) effect(() => {foo. Value2 = foo. Value1 foo.
The cleanup inside
Clear all dePs for this effect
The target is then collected again when fn () is executed
This is because it is possible to collect different things each time in effect, for example:
If(one reactive value === true) {console. log(another reactive value)}
It is obvious here that each time the FN of effect is executed, the tartGet it needs to collect is different
ActiveEffect = effectStack[effectStack.length-1]
Here are 5 not export value targetMap flattening is WeakMap < Target, Map < string | symbol, Set > > Target in front of us know that the raw data is to be monitored two-dimensional KeyToDepMap key, That’s the property key of the original object and Dep is the set that holds the listener effect
Track:
Here we can see that the value was inserted into targetMap during track
So we can see how effect knows what responsive data is being used internally when track is called after GET is triggered in the callback
Of course, it can also be seen that if an asynchronous function is used in effect, it will not use the reactive data in the asynchronous function
Dummy const obj = reactive({prop: 1 }) effect(() => { setTimeout(() => { dummy = obj.prop }, 1000) }) obj.prop = 2
Obj in this case cannot be detected by effect
The Trigger: This logic is easy to guess, because the targetMap has already collected the used reactive data. If the data is changed, the targetMap will retrieve the effect from the Map based on the key changed by the target
The Trigger code is too long to cut the full graph
It is not hard to see that dependencies are recollected when trigger is used, because the effect that needs to be executed is probably different each time the type of change is made.
effect ! ActiveEffect this is used to avoid infinite loops, such as foo.value++
And as you can see
Do different collection for different cases, and you can see that the effect collected for the array addition case is length
The length of the array is tracked when ownKeys are triggered
That is, some recursive operation on the array will trigger the response