A Proxy-based Observer
1 What is proxy
Proxy objects are used to define custom behavior for basic operations (such as property lookup, assignment, enumeration, function calls, and so on).
Proxy is a new feature of ES6. In order to function on the target, proxy mainly intercepts some behaviors of the target object (such as property lookup, assignment, enumeration, function call, etc.) by intercepting methods in the Handler object.
/* target: the target object to be wrapped with the Proxy. (This can be any type of object, including native arrays, functions, or even another Proxy.) * /
/* handler: an object whose properties are functions that define the behavior of the proxy when performing various operations. * /
const proxy = new Proxy(target, handler);
Copy the code
2. Why use proxy? What are the advantages and disadvantages of using proxy
** 3.0 will bring a proxy-based observer implementation that provides a full range of responsive capabilities across languages, Some limitations based on Object. DefineProperty in Vue 2 series have been eliminated. These limitations include: 1. 2. Subscription-based modification of array and monitoring of.length modification; 3. Support for Map, Set, WeakMap and WeakSet;
Vue2.0 uses Object. DefineProperty as the implementation of the response principle, but it will have its limitations, such as cannot listen to the array based on the subindex modification, does not support Map, Set, WeakMap and WeakSet and other defects, The use of proxy solves these problems, which also means that vue3.0 will abandon compatibility with older browsers (compatible with ie11 and up).
Basic usage of hander objects in proxy
Traps used in vue3.0 responsiveness (more on that next)
Handler for handler. Has () -> in operator. (used in vue3.0) handler.get() -> a trap for property read operations. (used in vue3.0) handler.set() -> property set * operation’s catcher. (used by VUe3.0) handler.deleteProperty() -> delete operator. Use) (vue3.0 handler. OwnKeys () – > Object. GetOwnPropertyNames method and Object getOwnPropertySymbols trap method. (vue3.0 used)
Vue3.0 reactive trap not used (interested students can study it)
Handler.getprototypeof () -> object.getPrototypeof method catcher. Handler. SetPrototypeOf () -> object.setPrototypeof method catcher. Handler.isextensible () -> Object. IsExtensible () method traps. Handler. PreventExtensions () – > Object. PreventExtensions trap method. Handler. GetOwnPropertyDescriptor () – > Object. GetOwnPropertyDescriptor trap method. Handler.defineproperty () -> handler for the object.defineProperty method. Handler.apply () -> Trap for function call operations. Construct () -> new operator.
① Has catcher
has(target, propKey)
Target: Indicates the target object
PropKey: name of the property to intercept
Action: intercepts an operation to determine if the target object contains the propKey attribute
Interception operation: propKey in proxy; Does not contain the for… In circulation
2. Reflect: Reflect. Has (target, propKey)
🌰 examples:
const handler = {
has(target, propKey){
/* * Do your operation */
return propKey in target
}
}
const proxy = new Proxy(target, handler)
Copy the code
② Get trap
get(target, propKey, receiver)
Target: Indicates the target object
PropKey: name of the property to intercept
Receiver: the proxy instance
Return: Returns the property read
Function: intercepts the reading of object properties
Interception operation: proxy[propKey] or dot operator
Get (target, propertyKey[, receiver])
🌰 examples:
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 'No such fruit'; }}const foot = new Proxy({}, handler)
foot.apple = 'apple'
foot.banana = 'banana';
console.log(foot.apple, foot.banana); /* Apple banana */
console.log('pig' in foot, foot.pig); /* false no fruit */
Copy the code
A special case
const person = {};
Object.defineProperty(person, 'age', {
value: 18.writable: false.configurable: false
})
const proxPerson = new Proxy(person, {
get(target,propKey) {
return 20
// Should return 18; Cannot return other values, otherwise an error will be reported}})console.log( proxPerson.age ) /* Error */ is reported
Copy the code
③ Set catcher
set(target,propKey, value,receiver)
Target: Indicates the target object
PropKey: name of the property to intercept
Value: new value of the property
Receiver: the proxy instance
Returns: true in strict mode the operation succeeded; Otherwise, an error is reported
Intercepts property assignment operations on an object
Intercept operation: proxy[propkey] = value
Reflect: Reflect. Set (obj, prop, value, receiver)
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) { /* If the age is not an integer */
throw new TypeError('The age is not an integer')}if (value > 200) { /* Out of the normal age range */
throw new RangeError('The age seems invalid')
}
}
obj[prop] = value
// Indicates success
return true}}let person = new Proxy({}, validator)
person.age = 100
console.log(person.age) / / 100
person.age = 'young' Uncaught TypeError: The age is not an INTEGER
person.age = 300 // Throw an exception: Uncaught RangeError: The age seems invalid
Copy the code
When the object property writable is false, the property cannot be modified in an interceptor
const person = {};
Object.defineProperty(person, 'age', {
value: 18.writable: false.configurable: true});const handler = {
set: function(obj, prop, value, receiver) {
return Reflect.set(... arguments); }};const proxy = new Proxy(person, handler);
proxy.age = 20;
console.log(person) // {age: 18} Indicates that the modification failed
Copy the code
④ deleteProperty catcher
deleteProperty(target, propKey)
Target: Indicates the target object
PropKey: name of the property to intercept
Returns: in strict mode only true is returned, otherwise an error is reported
Action: Intercepts the deletion of the propKey property of the target object
Delete proxy[propKey]
Reflect: Reflect. Delete (obj, prop)
var foot = { apple: 'apple' , banana:'banana' }
var proxy = new Proxy(foot, {
deleteProperty(target, prop) {
console.log('Currently deleted fruit :',target[prop])
return delete target[prop]
}
});
delete proxy.apple
console.log(foot)
/* Run result :' Currently deleted fruit: apple '{banana:' banana'} */
Copy the code
Special case: If the attribute is a non-configurable attribute, it cannot be deleted
var foot = { apple: 'apple' }
Object.defineProperty(foot, 'banana', {
value: 'banana'.configurable: false
})
var proxy = new Proxy(foot, {
deleteProperty(target, prop) {
return deletetarget[prop]; }})delete proxy.banana /* No effect */
console.log(foot)
Copy the code
(5) ownKeys capture device
ownKeys(target)
Target: Indicates the target object
Return: array (array elements must be characters or symbols, other types report error)
Function: Intercepts operations that retrieve key values
Intercept operation:
1 Object.getOwnPropertyNames(proxy)
2 Object.getOwnPropertySymbols(proxy)
3 Object.keys(proxy)
4 for… in… cycle
Corresponding Reflect: Reflect.ownkeys ()
var obj = { a: 10[Symbol.for('foo')]: 2 };
Object.defineProperty(obj, 'c', {
value: 3.enumerable: false
})
var p = new Proxy(obj, {
ownKeys(target) {
return [...Reflect.ownKeys(target), 'b'.Symbol.for('bar')]}})const keys = Object.keys(p) // ['a']
// Automatically filter out Symbol/ non-self/non-traversable properties
/* As with object.keys (), only the traversable properties of the target itself are returned
for(let prop in p) {
console.log('prop-',prop) /* prop-a */
}
/* Only return non-symbol properties returned by the interceptor, regardless of whether they are properties on target */
const ownNames = Object.getOwnPropertyNames(p) /* ['a', 'c', 'b'] */
/* Return only the properties of the Symbol returned by the interceptor, regardless of whether the properties are on the target */
const ownSymbols = Object.getOwnPropertySymbols(p)// [Symbol(foo), Symbol(bar)]
/* Return all values returned by the interceptor */
const ownKeys = Reflect.ownKeys(p)
// ['a','c',Symbol(foo),'b',Symbol(bar)]
Copy the code
How does VUe3.0 establish a response
There are two methods to establish response in VUe3.0: The first one is to build reactive structures directly using the reactive API, which we can use in our.vue file, setup() function to process most of the logic we’ve been doing before. Instead of declaring the lifecycle in export Default {}, data(){}, watch{}, computed{}, etc., we use the setup function, Use the Vue3.0 Reactive Watch Lifecycle API to achieve the same effect, which is similar to the react-hooks approach to code reuse.
The second is to use the traditional data(){return{}} form,vue3.0 does not abandon the vue2.0 writing support, but is fully compatible with vue2.0 writing, providing applyOptions to handle the options form of vUE components. However, for data, Watch, and computed in options, the API in Comaction-API is used for processing.
1 composition-api reactive
Reactive is equivalent to the current VUe.Observable () API. Functions processed by Reactive can become Reactive data, similar to the return value of Vue processing data in the Option API.
Let’s try it out with a todoList demo.
const { reactive , onMounted } = Vue
setup(){
const state = reactive({
count:0.todoList:[]
})
/* Life cycle mounted */
onMounted(() = > {
console.log('mounted')})/* Increase count */
function add(){
state.count++
}
/* Reduce count */
function del(){
state.count--
}
/* Add to-do items */
function addTodo(id,title,content){
state.todoList.push({
id,
title,
content,
done:false})}/* Complete the to-do */
function complete(id){
for(let i = 0; i< state.todoList.length; i++){
const currentTodo = state.todoList[i]
if(id === currentTodo.id){ state.todoList[i] = { ... currentTodo,done:true
}
break}}}return {
state,
add,
del,
addTodo,
complete
}
}
Copy the code
2 options data
Options is not different from vue2.0, right
export default {
data(){
return{
count:0.todoList: []}},mounted(){
console.log('mounted')}methods: {add(){
this.count++
},
del(){
this.count--
},
addTodo(id,title,content){
this.todoList.push({
id,
title,
content,
done:false})},complete(id){
for(let i = 0; i< this.todoList.length; i++){
const currentTodo = this.todoList[i]
if(id === currentTodo.id){
this.todoList[i] = { ... currentTodo,done:true
}
break
}
}
}
}
}
Copy the code
A preliminary study on the three-response principle
Different types of Reactive
Vue3.0 can introduce different API approaches according to business requirements. There needs to be
1) reactive
Reactive is created and returns a proxy object. This reactive object can be recursed deeply, that is, if it is found that the value of the expanded attribute is a reference type and is referenced, it will be recursed with reactive. And the properties can be modified.
(2) shallowReactive
Create a shallowReactive object that returns a proxy object. The difference between reactive and reactive is that only one level of reactive is created. This means that if an expanded attribute is found to be a reference type, it will not recurse.
(3) readonly
The returned object handled by the proxy can be expanded recursively, but the properties are read-only and cannot be modified. You can pass props to child components.
(4) shallowReadonly
Returns the processed proxy object, but the creation of the responsive properties is read-only, and does not expand the reference or recursively transform it. This can be used to create props for stateful components.
Store objects and proxies
We mentioned above. The object returned by Reactive is a proxy object. If there are many components, or if a component is Reactive multiple times, there will be many primitive objects for the proxy object and its proxies. In order to establish the relationship between proxy object and original object, VUe3.0 uses WeakMap to store these object relationships. WeakMaps maintains a weak reference to the object referenced by the key name, that is, the garbage collection mechanism does not take the reference into account. Garbage collection frees the memory used by the referenced object as long as all other references to the object are cleared. In other words, once no longer needed, the key name object and the corresponding key value pair in the WeakMap will disappear automatically, without manually deleting the reference.
const rawToReactive = new WeakMap<any, any>()
const reactiveToRaw = new WeakMap<any, any>()
const rawToReadonly = new WeakMap<any, any>() /* Read-only */
const readonlyToRaw = new WeakMap<any, any>() /* Read-only */
Copy the code
Vue3.0 uses readonly to set whether an object intercepted by an interceptor can be modified. This can be used in a one-way data flow scenario where the props cannot be modified. Let’s focus on the storage relationships of the next four WeakMaps.
rawToReactive
Key/value pairs: {[targetObject] : obseved}
Target (key) : the value of the target object (the first parameter of reactive). Obsered (value) : proxy object after proxy.
ReactiveToRaw reactiveToRaw stores the opposite key-value pair to rawToReactive. Key-value pair {[obseved] : targetObject}
rawToReadonly
{[target] : obseved}
Target (key) : indicates the target object. Obsered (value) : A proxy object with read-only properties after proxy.
ReadonlyToRaw The storage state is the opposite of rawToReadonly.
Reactive entry analysis
So let’s start with reactive.
reactive({ … The object}) entrance
/ *TODO:* /
export function reactive(target: object) {
if (readonlyToRaw.has(target)) {
return target
}
return createReactiveObject(
target, /* Target object */
rawToReactive, /* { [targetObject] : obseved } */
reactiveToRaw, /* { [obseved] : targetObject } */
mutableHandlers, /* Handles basic and reference data types */
mutableCollectionHandlers /* Used to process Set, Map, WeakMap, WeakSet type */)}Copy the code
The reactive function generates a proxy by using the createReactiveObject method, and it has different processing methods for different data types.
createReactiveObject
So with that createReactiveObject, let’s look at what happens to the createReactiveObject.
const collectionTypes = new Set<Function> ([Set.Map.WeakMap.WeakSet])
function createReactiveObject(
target: unknown,
toProxy: WeakMap<any, any>,
toRaw: WeakMap<any, any>,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
/* Determine whether the target object is affected by the effect */
/* Observed is a function */ that passes through new Proxy
let observed = toProxy.get(target) /* { [target] : obseved } */
if(observed ! = =void 0) { /* If the target object has been processed responsively, the observed object of proxy is returned directly */
return observed
}
if (toRaw.has(target)) { /* { [observed] : target } */
return target
}
/* If the target object is Set, Map, WeakMap, or WeakSet, then the hander function is collectionHandlers
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
/ *TODO:Create a reactive object */
observed = new Proxy(target, handlers)
/* Establish association between target and Observed */
toProxy.set(target, observed)
toRaw.set(observed, target)
/* Return observed object */
return observed
}
Copy the code
Through the above source code to create the general process of proxy object is like this: first of all, the target object to determine whether the proxy response type proxy, if it is then directly return the object. (2) Select collectionHandlers by determining whether the target object is a [Set, Map, WeakMap, WeakSet] data type. So, baseHandlers-> reactive, so the mutableHandlers are passed in as the hander object of the proxy. (3) Finally, create a Observed by using new Proxy and save the key value pair of target and Observed by rawToReactive reactiveToRaw.
General flow chart:
Four interceptor objects baseHandlers -> mutableHandlers
So we’ve seen before that baseHandlers are just mutableHandlers that are passed in by calling reactive createReactiveObject. Let’s look at the mutableHandlers object first
mutableHandlers
The scope of the interceptor
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
Copy the code
The above interceptors are used in VUe3.0. We have introduced the basic usage of these interceptors in the previous section. First, we will review some of the basic interceptors.
Get: intercepts data read properties including target. Dot syntax and target[]
②set, intercept the attributes of data stored.
③ The deleteProperty delete operator intercepts.
Vu 2.0 cannot intercept properties of delete operators on objects.
Example 🌰 :
delete object.a
Copy the code
It’s not detectable.
DeleteProperty in VUe3.0 proxy can intercept the delete operator, which indicates that vue3.0 responsiveness can listen to the deletion operation of attributes.
④ Has: intercepts properties of the in operator.
Vue2.0 cannot intercept properties of the in operator of an object.
example
a in object
Copy the code
Has is to solve these problems. This means that vue3.0 can intercept the in operator.
(5) ownKeys Object. Keys (proxy), for… in… Circular Object. GetOwnPropertySymbols (proxy), Object getOwnPropertyNames (proxy) interceptor
example
Object.keys(object)
Copy the code
Vue3.0 can intercept the preceding methods.
Five component initialization phases
If we want to understand the whole response principle. In this case, component initialization is inseparable from the processing of data by comjunction-API reactive during initialization and the collection of dependent data attributes during compilation. Vue3.0 provides a fully responsive system from initialization, to dependency collection, to component update, to component destruction. It’s hard to explain everything from one point of view, so let’s see what Effect does before we get into how the interceptor object collects dependencies and dispatchupdates.
1 Effect -> New render watcher
Vue3.0 replaces the vue2.0watcher with the effect side effect hook. We all know that in vue2.0, there is a rendering watcher which is responsible for the new rendering view after the data changes. Vue3.0 uses effect instead of watcher to achieve the same effect.
Let’s start with a brief introduction to the mountComponent process, and a later article will cover the mount phase in more detail
1 mountComponent Initializes the mountComponent
// Initialize the component
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
/* Step 1: Create a Component instance */
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
/* Step 2:TODO:Initializes the component, establishes the proxy, and obtains */ according to the character template
setupComponent(instance)
/* Step 3: Create a render effect and execute effect */
setupRenderEffect(
instance, // Component instance
initialVNode, //vnode
container, // The container element
anchor,
parentSuspense,
isSVG,
optimized
)
}
Copy the code
Step 1: Create an instance of Component. Step 1: Create an instance of Component. Step 2: Create an instance of Component. Step 2: initialize the component, establish proxy, according to the character channling template to get the render function. Step 3: Create a render effect and execute the effect.
As you can see from the above method, the responsive object has been built in the setupComponent, but the collection dependency has not been initialized.
SetupRenderEffect builds the render effect
const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
Create a render effect */
instance.update = effect(function componentEffect() {
/ /... The omissions will come later}, {scheduler: queueJob })
}
Copy the code
To give you a clearer idea of the responsive principle, I have reserved only the part of the code that relates to the responsive principle.
The role of setupRenderEffect
Create an effect and assign it to the component instance’s update method for rendering the update view. (2) componentEffect is passed to effect as the first parameter as a callback function
3 What does Effect do
export function effect<T = any> (fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ) :ReactiveEffect<T> {
const effect = createReactiveEffect(fn, options)
/* Execute effect */ if the load is not lazy
if(! options.lazy) { effect() }return effect
}
Copy the code
The effect is as follows
① First call. CreateReactiveEffect 2 execute the ReactiveEffect function created by createReactiveEffect immediately if it is not lazily loaded
4 ReactiveEffect
function createReactiveEffect<T = any> (fn: (... args: any[]) => T,/** callback function */
options: ReactiveEffectOptions
) :ReactiveEffect<T> {
const effect = function reactiveEffect(. args: unknown[]) :unknown {
try {
enableTracking()
effectStack.push(effect) // Insert the current effect into the effect array
activeEffect = effect //TODO:Effect is assigned to the current activeEffect
returnfn(... args)//TODO:Fn for effect componentEffect
} finally {
effectStack.pop() // Remove the effect from the effect array after the dependency collection is complete
resetTracking()
Restore activeEffect to previous effect */
activeEffect = effectStack[effectStack.length - 1]}}as ReactiveEffect
/* Set initialization parameters */
effect.id = uid++
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = [] / *TODO:Used to collect related dependencies */
effect.options = options
return effect
}
Copy the code
createReactiveEffect
CreateReactiveEffect sets the initialization parameters and wraps the fn passed in earlier. The important thing is to assign the current effect to the activeEffect. This is very important and directly related to the collection dependency
Which leaves a mystery here,
① Why do I use an effectStack array to store effect
conclusion
Let’s conclude with a reactive initialization phase
① setupComponent creates a component, calls composition-API, and processes options (builds responsiveness) to get an Observer object.
Create a rendering effect, which wrapped the real rendering method componentEffect, add some effect initialization properties.
(3) then execute effect immediately and assign the current render effect to activeEffect
Finally, let’s use a picture to explain the process.
Six dependent collection, what does GET do?
Return to the GET method in mutableHandlers
1 Different types of GET
/* Get */
const get = /*#__PURE__*/ createGetter()
Shallow get * / / *
const shallowGet = /*#__PURE__*/ createGetter(false.true)
/* Read only get */
const readonlyGet = /*#__PURE__*/ createGetter(true)
/* Read-only shallow get */
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true.true)
Copy the code
As we can see above, for the four different methods of setting up the response, there are four different kinds of GET, and the following is a one-to-one correspondence.
reactive ———> get
shallowReactive ——–> shallowGet
readonly ———-> readonlyGet
shallowReadonly —————> shallowReadonlyGet
All four methods call the createGetter method, but the parameters are configured differently. Let’s explore the createGetter method.
createGetter
function createGetter(isReadonly = false, shallow = false) {
return function get(target: object, key: string | symbol, receiver: object) {
const res = Reflect.get(target, key, receiver)
/* Shallow logic */
if(shallow) { ! isReadonly && track(target, TrackOpTypes.GET, key)return res
}
/* Data binding */! isReadonly && track(target, TrackOpTypes.GET, key)return isObject(res)
? isReadonly
?
/* Read-only property */
readonly(res)
/ * * /
: reactive(res)
: res
}
}
Copy the code
This is the main process of createGetter, and we’ll leave out the special data types and refs for now. There’s a little bit of flow judgment here, so let’s use a flow chart to illustrate what this function basically does. Okay?
We can conclude that at vue2.0. The response is processed deeply recursively at initialization but
Unlike vue2.0, even deep responsiveness can only trigger the next level of deep responsiveness after getting the upper get. Such as
setup(){
const state = reactive({ a: {b: {}}})return {
state
}
}
Copy the code
During initialization, only the first level of A has a response, but b does not have a response. When we use state.a, we will actually treat B as a response. That is to say, after we access the attributes of the upper level, the attributes of the next generation will actually have a response
The advantage of this is that 1 initializes without recursively processing the object, resulting in unnecessary performance overhead. *2 has some states that are not used, so there is no need to respond at a deep level.
2 track-> Dependent collector
Let’s first look at the source of Track:
What does Track do
/* The target object itself. The key attribute value is type 'GET' */
export function track(target: object, type: TrackOpTypes, key: unknown) {
Console. log(this.a) has no activeEffect when printing or retrieving properties. The current return value is 0 */
let depsMap = targetMap.get(target)
if(! depsMap) {/* target -map-> depsMap */
targetMap.set(target, (depsMap = new Map()))}let dep = depsMap.get(key)
if(! dep) {Key: dep dep observer */
depsMap.set(key, (dep = new Set()))}/* activeEffect */
if(! dep.has(activeEffect)) {/* dep add activeEffect */
dep.add(activeEffect)
/* each activeEffect deps stores the current dep */
activeEffect.deps.push(dep)
}
}
Copy the code
It mainly introduces two concepts: targetMap and depsMap
TargetMap Key-value pair proxy: depsMap Proxy: an Observer object created by reactive proxy. DepsMap: stores the map that depends on the DEP.
DepsMap Key and value pairs: Key: deps Key is the name of the property accessed by GET. Deps stores the set data type of effect.
As we know, track functions roughly as follows: firstly, obtain depsMap storing DEPs according to proxy object, then obtain corresponding DEP through the accessed attribute name key, and then store the currently activated effect into the current DEP to collect dependencies.
Main Function ① Find the DEP corresponding to the current proxy and key. (2) dep establishes a relationship with the current activeEffect and collects dependencies.
To understand the relationship between targetMap and depsMap, let’s use an example: Example: parent component A
<div id="app" >
<span>{{ state.a }}</span>
<span>{{ state.b }}</span>
<div>
<script>
const { createApp, reactive } = Vue
/* Subcomponent */
const Children ={
template="<div> <span>{{ state.c }}</span> </div>".setup(){
const state = reactive({
c:1
})
return {
state
}
}
}
/* Parent component */
createApp({
component:{
Children
}
setup(){
const state = reactive({
a:1.b:2
})
return {
state
}
}
})mount('#app')
</script>
Copy the code
Let’s use a picture to show the above relationship:
How does the render effect function trigger get
So we said before, create a render renderEffect, assign it to the activeEffect, and then execute the renderEffect, and how do we do that between now and then depends on collection, so let’s look at what’s going on in the update function, Let’s go back to componentEffect logic
function componentEffect() {
if(! instance.isMounted) {let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, a, parent } = instance
/ *TODO:Trigger the instance.render function, forming a tree structure */
const subTree = (instance.subTree = renderComponentRoot(instance))
if (bm) {
// Triggers beforeMount to declare periodic hooks
invokeArrayFns(bm)
}
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
/* Triggers the declaration cycle mounted hook */
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
instance.isMounted = true
} else {
// Update the component logic
/ /...}}Copy the code
In this case, the code will first form a tree structure with the renderComponentRoot method. It should be noted that we have compiled the contents of the template template with the compile method in the setupComponent method of the original mountComponent. In the render function, the expression state. A state. B in the template of the render function will be replaced by the actual attributes in the data, and then the real dependency collection will take place. The GET method is triggered. The next step is to trigger the life cycle beforeMount, and then re-patch the entire tree structure. After the patch is complete, the mounted hook is called
Rely on the collection process summary
First execute renderEffect, assign to activeEffect, call renderComponentRoot method, and then trigger render function.
(2) According to the render function, parse the template expression processed by compile and syntax tree, access the real data attribute, and trigger GET.
(3) The GET method first passes through the previous different reactive, and then relies on the TRACK method for dependency collection.
The track method uses the current proxy object target and the accessed attribute name key to find the corresponding DEP.
⑤ establish a connection between dep and the current activeEffect. We push the activeEffect into the dep array (the dep already contains the rendering effect of the current component, which is the root cause of responsiveness). If we trigger the set, we can find the corresponding effect in the array and execute it in turn.
Finally, let’s use a flowchart to express the process of relying on collection.
Seven set dispatches updates
Next we set part of the logic.
const set = /*#__PURE__*/ createSetter()
/* Shallow logic */
const shallowSet = /*#__PURE__*/ createSetter(true)
Copy the code
Set is also divided into two types of logic, set and shallowSet. Both methods are generated by the createSetter.
Create the set createSetter
function createSetter(shallow = false) {
return function set(target: object, key: string | symbol, value: unknown, receiver: object) :boolean {
const oldValue = (target as any)[key]
/* shallowSet logic */
const hadKey = hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
/* Determine whether the current object and reactiveToRaw are equal */
if (target === toRaw(receiver)) {
if(! hadKey) {/* Create new properties */
/* TriggerOpTypes.ADD -> add */
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
/* Change the original properties */
/* TriggerOpTypes.SET -> set */
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
Copy the code
The createSetter process goes something like this
(1) Firstly, determine whether the current proxy object is equal to the proxy object that is stored in reactiveToRaw by toRaw. (2) Determine if the target has a current key. If so, change the attribute and trigger(target, triggeroptypes. SET, key, value, oldValue). Trigger (target, triggeroptypes. ADD, key, value);
trigger
Var var var var var var var var var var var var var var var var var var var
export function trigger(target: object, type: TriggerOpTypes, key? : unknown, newValue? : unknown, oldValue? : unknown, oldTarget? :Map<unknown, unknown> | Set<unknown>
) {
/* Get depssMap */
const depsMap = targetMap.get(target)
Return */ if no dependencies have been collected
if(! depsMap) {return
}
const effects = new Set<ReactiveEffect>() /* effect hook queue */
const computedRunners = new Set<ReactiveEffect>() /* Calculate the attribute queue */
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) = > {
if (effectsToAdd) {
effectsToAdd.forEach(effect= > {
if(effect ! == activeEffect || ! shouldTrack) {if (effect.options.computed) { /* Processes computed logic */
computedRunners.add(effect) /* Store dep */
} else {
effects.add(effect) /* Store dep */
}
}
})
}
}
add(depsMap.get(key))
const run = (effect: ReactiveEffect) = > {
if (effect.options.scheduler) { /* Put scheduler */
effect.options.scheduler(effect)
} else {
effect() /* Effect */ is executed directly if no scheduling situation exists}}//TODO:You must first run an update to the evaluated property in order to evaluate the getter
// May fail before any normal update effect that depends on them runs.
computedRunners.forEach(run) /* Execute computedRunners callback */ in turn
effects.forEach(run) /* Execute the effect callback (TODO:This includes rendering effect) */
}
Copy the code
We have retained the core logic of trigger here
① Firstly, from the targetMap, find the depsMap corresponding to the current proxy. ② Locate the dePs in depsMap based on the key, and then separate the effect callback function and computed callback function using the add method. (3) Execute the computedRunners and the effects call-back functions in sequence. If the scheduler event is needed, put it in the Scheduler event
It is worth noting that:
The effect queue contains the renderEffect that we are responsible for rendering, the effect created through the effectAPI, and the effect created through the Watch. We only consider the render effect here. As for the following situation, I will share with you in the following article.
Let’s illustrate the set process with a flow chart.
Eight summary
Let’s conclude that the whole data binding establishment response is roughly divided into three phases
1. Initialization phase: In the initialization phase, the corresponding proxy object is formed through the component initialization method, and then an effect is formed responsible for rendering.
2. Get dependency collection phase: Get is triggered by parsing the template and replacing the real data attribute. Then, through stack method, proxy object and key are used to form the corresponding DEPS, and the effect responsible for rendering is stored in dePS. (this process also has other effects, such as watchEffect, stored in deps).
3 Set dispatch and update phase: When we change the property of this[key] = value, we first find the corresponding dePs through the proxy object and key through the trigger method, then divide the dePs into computedRunners and effects, and then execute the dePs in sequence. If the dePs need to be scheduled, Directly into scheduling.
Wechat scan code to follow the public account, regularly share technical articles