“This is the 41st day of my participation in the First Challenge 2022.
Previously on & Background
This essay describes how Vue handles the custom component createComponent. The most important thing to understand here is that child components are not created directly by Vue, but by subclasses of Vue;
In our daily development, we write.vue files as an example. In script, we export just one option object, but even if we write multiple components, they do not affect each other, because multiple components are multiple instances, and instances are naturally isolated from each other. Instantiation is created by the constructor, which is a subclass of the extension of the option object we wrote;
Init, prepatch, Insert, and destroy. Init is responsible for the initialization of subcomponent instances. This process is equivalent to the whole process of new Vue. The process of creating template compilation and rendering functions that contain subcomponents;
Today we talk about the next topic — rendering Watcher. This article will focus on the dependency collection of data from templates in rendering Watcher and Vue.
Second, the Vue. Prototype. $mount
Var.prototype.$mount was rewritten to include the render function before mounting. This logic is not available in the non-compiled version.
// Cache the original $mount, which does not contain the logic to compile the template into a rendering function
const mount = Vue.prototype.$mount
// Override the $mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
) :Component {
if(! options.render) {if (template) {
const{ render, staticRenderFns } = compileToFunctions(template, {.. },this)
// Put two rendering functions on vm.$options,
$options for vue.prototype. _render
options.render = render
options.staticRenderFns = staticRenderFns
}
}
// Execute the mount, which will be the focus of today
// Mount is vue.prototype.$mount
return mount.call(this, el, hydrating)
}
Copy the code
Three, mount method
$mount = Vue. Prototype $mount;
Methods location: SRC/platforms/web/runtime/index. The js – > Vue. Prototype. $mount
Method parameters:
el
, mount point element in the pagediv#app
hydrating
: Ignore him
The mountComponent () method creates the render Watcher, which executes the render function to render elements to the page.
Vue.prototype.$mount = function ( el? : string | Element, hydrating? : boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }Copy the code
Third, mountComponent
Methods location: SRC/core/instance/lifecycle. Js – > function mountComponent
Method parameters:
vm
.Component
.el
Mount elements in the pagediv#app
hydrating
, ignore it
Methods:
- The trigger
beforeMount
Lifecycle hooks; - The statement
updateComponent
Method,updateComponent
Is used to pass toRender the watcher
Method within that methodvm._render
methodVirtual DOM
And pass tovm._update
Methods into thepatch
Stage; whenRender the watcher
The dependent data changes when thisupdateComponent
It’s also called to update the view; - That’s what we got in the previous step
updateComponent
Method to createRender the watcher
Instance,Render the watcher
And that’s what we used in the data reactive formWatcher
Same thing, bothWatcher
Examples of, but hisexpOrFn
Is responsible for renderingupdateComponent
Methods; - Trigger manual mount yes
mounted
Lifecycle hooks;
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
vm.$el = el
if(! vm.$options.render) { vm.$options.render = createEmptyVNodeif(process.env.NODE_ENV ! = ='production') { // No render function throws warning}
}
callHook(vm, 'beforeMount')
let updateComponent
if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
// ...
} else {
// updateComponent is the core
updateComponent = () = > {
// Execute vm._render() to get a virtual VNode.
// Pass the VNode to the vm._update method
vm._update(vm._render(), hydrating)
}
}
// This thing is rendering watcher
// When initializing the render watcher, the constructor of the Watcher class decides,
// To render watcher, mount watcher to the vm instance: vm._watcher
// This is done because rendering watcher initialization is possible
// The $forceUpdate method may be called when it is called,
// In the mounted lifecycle hook of a child component,
// The dependency vm._watcher is already defined
new Watcher(vm, updateComponent, noop, {
before () {
if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* true indicates that the watcher is rendering watcher */)
hydrating = false
// Manually mount the instance to call the VM mounted hook
// The mounted hook of the child component created by the render function will be called in the inserted hook of the component
Inserted Hook is the previous createComponent installComponentHooks
// Add hooks to the component's data, including init, prepatch, insert, destroy hooks
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')}return vm
}
Copy the code
3.1 updateCompnent
UpdateComponent is an intermediate method, used as the expOrFn parameter for creating render Watcher, this parameter is the method to be executed when watcher is evaluated; For rendering Watcher, evaluation is the operation of mounting the virtual DOM to the page.
let updateComponent
/ /...
updateComponent = () = > {
// Execute the vm._render() function to get the virtual VNode
// VNode passes the vm._update method to patch
vm._update(vm._render(), hydrating)
}
Copy the code
3.2 Create render Watcher
new Watcher(
vm,
updateComponent,
noop,
{
before () {
if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* Render watcher identifier */
)
Copy the code
3.3 see Watcher
export default class Watcher {
// ...
constructor (
vm: Component, // corresponds to the VM passed above
expOrFn: string | Function.// updateComponent above
cb: Function.// noop is ignored and is an empty function
options?: ?Object.{before () {... }} objectisRenderWatcher? : boolean// Whether to render watcher, which corresponds to true above
) {
this.vm = vm
if (isRenderWatcher) {
// If it is a render Watcher then mount it to the VM
// The mountComponent mentioned above is intended for a child component that calls vm.$forceUpdate
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
// Mount the passed before method to the current watcher instance
this.before = options.before
} else {
// ...
}
if (typeof expOrFn === 'function') {
// expOrFn is the updateComponent method
this.getter = expOrFn
} else {
/ /...
}
// Evaluate watcher, expOrFn,
// For rendering Watcher, updateComponent
// this.lazy We've only seen true in computed attributes, usually false
this.value = this.lazy
? undefined
: this.get() // The get method is below, which calls the this.getter method
}
// Execute this.getter, which is the updateComponent method
Getter is the updateComponent passed when watcher is instantiated
// Collect dependencies in the template
get () {
// Open dep. target, dep. target = this, this is an instance of Watcher
// This place is used for collecting dependencies below
pushTarget(this)
let value
const vm = this.vm
try {
// Execute the callback function, such as updateComponent, to enter the patch phase
value = this.getter.call(vm, vm)
} catch (e) {
} finally {
if (this.deep) {
traverse(value)
}
// Dep.target = null
popTarget()
this.cleanupDeps()
}
return value
}
//....
}
Copy the code
3.3.1 Watcher. Get & Dependency collection
We talked about Watcher earlier when we talked about data responsiveness, but why do we need to talk about it again? This is because the focus is different, and here we are talking about rendering Watcher.
Bind :xx/v-bind:xx/{{xx}}; bind:xx/ v-bind:xx/{{xx}} This step is crucial in explaining why the page is updated after the data is updated.
First of all, let’s outline the call process:
Getter = updateComponent -> this.get -> this.getter Equivalent to updateComponentCopy the code
updateComponent
Just do one simple thing, callvm._render()
getVNode
And then pass it tovm._update()
Method, the operation goes intopatch
Stage, so calledpatch
Update (new in this case)v-dom
Tree, and mount it to the page
updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
Copy the code
Vm._render () calls the render function we got earlier…
Of course, it takes time before the render function is called, so let’s talk about dependency collection first.
Let’s look at some render function code:
/ / processing < span
// v-for="item in someArr"
// :key="index">{{item}}
// This is an example
function () {
return with (this) {
_l(
(someArr), // someArr is data on data
function (item) {
return _c(
'span',
{ key:index },
[
_v(_s(item))
]
)
}
)
}
}
Copy the code
This. SomeArr (with this) accesses the Vue instance this.somearr, SomeArr = someArr; this.somearr = someArr; this.somearr = someArr; this.somearr = someArr;
Getters and setters (someArr); getters (someArr); getters (someArr);
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function, shallow? : boolean) {
// Create a Dep instance, which will be used by depend on the Dep below
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.// get intercepts the obj[key] read operation
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// Depending on the collection, add watcher to the DEP and add deP to the watcher
dep.depend()
}
return value
},
// set intercepts the set operation on obj[key]
set: function reactiveSetter (newVal) {}})}Copy the code
3.3.2 Dep. depend & Depend collection
PushTarget (this) in wather.prototype. get is combined with Dep. depend in Object.defineProperty.
export default class Dep {
// dep. target is pushTarget(watcher)
// Add deP to watcher
// Put the observer into the observed
depend () {
if (Dep.target) {
/ / Dep. Target. AddDep is
// watcher.prototype. addDep is simply an instance of Dep
// Where did you get it? Const dep = new dep () in defineProperty
Dep.target.addDep(this)}}// Add watcher to deP
addSub (sub: Watcher) {
this.subs.push(sub)
}
}
Copy the code
3.3.3 wathcer.adddep & dependency collection
As you can see from the defineReactive method above, each data defined as reactive has a DEP instance that is then executed via ep.depend, Ep. denpend calls dep.target.addDep (wather.prototype. addDep)
The addDep method does this:
- Take this data
dep
Added to thewatcher.newDeps
In the array; - through
dep.push
To put thiswatcher
Added to thedep.subs
;
export default class Watcher {
// ...
// Two things:
// 1. Add dep to self watcher
// 2. Add yourself (watcher) to dep
addDep (dep: Dep) {
If the deP already exists, do not add it again
const id = dep.id
if (!this.newDepIds.has(id)) {
// Cache dep.id for weight determination
this.newDepIds.add(id)
this.newDeps.push(dep)
// Avoid repeating watcher in deP,
// This. DepIds is set in the cleanupDeps method
if (!this.depIds.has(id)) {
// Add Watcher itself to the deP
dep.addSub(this)}}}}Copy the code
Four,
The mount method is the core of the mountComponent method. This method creates the render Watcher. We reviewed the Render Watcher class and the Dep class.
- First of all, when we initialize the data in response
data.someArr
throughdefineReactive
It becomes a responsive data structure, so it becomes agetter/setter
And finally thesomeArr
The agent tovm
On; - We used an example above where the render function references a
someArr
Data for list rendering. performRender function
theRender the watcher
To perform thePushTarget (rendering watcher
),Dep.target
This is theRender the watcher
. - Then to render the list you need access
someArr
The value ofsomeArr
的getter
; getter
judgmentDep.target
The value of, and then thedep
In thewatcher.newDeps
Array, anddep
willRender the watcher
In thedep.subs
In the array.- If the data changes,
dep
Is responsible for tellingRender the watcher
, data changed, callwatcher.update
Method reevaluate, of course, that’s a later story;