preface
This is the fourth chapter of VUe2.0 analysis. The difficulties of this article are all in patch, and there is no difficulty in component registration.
The article links
Vue parse: data
Vue parse: computed
Vue d. watch
Components are the heart of VUE, and there are two ways to register components: global and local, according to the official documentation. So let’s look at global registration first, so let’s do an example
<div id="app">
<div>This is a div</div>
<comp-a></comp-a>
</div>
<script>
Vue.component('comp-a', {
template: '
this is a child component
'
})
const vm = new Vue({
el: '#app',})</script>
Copy the code
For global registration, we first need to understand how methods are mounted during vUE initialization. In the core/instance/index.js file, vue. prototype has a large number of methods mounted by combining methods that can be guessed by method names.
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
Copy the code
InitGlobalAPI (Vue) is then called in core/index.js, which is defined in core/global-api/index.js, and it mounts a number of methods, namely static methods, on Vue. So what did it do? Let’s look at the core implementation of this example
export function initGlobalAPI (Vue: GlobalAPI) {
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type= > {
Vue.options[type + 's'] = Object.create(null)})// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initExtend(Vue)
initAssetRegisters(Vue)
}
Copy the code
The middle forEach loop looks directly at the result, and we give __proto__ of each existing object a null pointer. And extend to blend keep-alive into components. It is then registered through initAssetRegisters. The components in this method is
Vue['component'] = function(id, definition) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
return definition
}
Copy the code
So in Vue it can be summed up as
Vue.options = {
components: {
KeepAlive
},
directives: Object.create(null),
filters: Object.create(null),
_base: Vue
}
Vue.extend = function() {}
Vue.component = function(id, definition) {}
Copy the code
Initialization of global registration
Let’s start with an example. Before new Vue, we used Vue.com Ponent to define global components, so Vue calls the static method Vue.component to initialize. This.options._base. Extend (definition). This.options._base is defined in initGlobalAPI as Vue. So here we call vue. extend to initialize the component. Ok, let’s go into vue. extend
Vue.extend = function (extendOptions: Object) :Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub
}
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}
Copy the code
This method is just as important as the init method, which is the creation of an instance of a component, but for this example we’ll just look at its core. And then summarize what it did.
- will
Vue
Assigned toSuper
- A cache is made, for example if we use the same component twice in a parent component, then the return is that the cache does not need to create the instance method again
- To create the
Sub
Instance method, the method’s__proto
Points to theVue
To point the constructor of a prototype at itself, standard prototype inheritance - Merge option to the parent component
components
And the incoming option of the child component, assigning the child component option;Pay attention to: of the parent componentassets
Is placed on the prototype of the child component, seemergeAssets
methods
Also use Object.create 5. initialize props and computed 6. Initialize some other methods 7. Return the constructor
Vue.com Ponent completes execution and starts the new Vue process
As before, the new Vue initialization creates the render Watcher, which is the second argument passed in when the get function in the Watcher is called. The updateComponent method executes vm._update(vm._render(), hydrating). The method is divided into two steps. The first step is to execute the render function to generate a VNode. The second step is to render the vNode to the page via vm.update.
Vnode = render. Call (vm._renderProxy, vm.$createElement)
; (function anonymous () {
with (this) {
return _c(
'div',
{ attrs: { id: 'app' } },
[_c('div', [_v('This is a div.')]), _v(' '), _c('comp-a')].1)}})Copy the code
And then we do _c(comp-a). We talked about _c earlier, remember? In initRender, there is a declaration vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false). Obviously we’re calling createElement.
export function createElement (context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean) :VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
Copy the code
In this method, data is judged, and we only pass in the first two parameters, the first is VM, and the second is comp-a. Then the intermediate assignment step is not performed and the last value is false. When it is true. This function is triggered when we use the render function instead of the template, just so we know. Let’s look at the _createElement method. This method is in core/vnode/create-element.js
export function _createElement (context: Component, tag? : string | Class<Component> |Function | Object, data? : VNodeData, children? : any, normalizationType? : number) :VNode | Array<VNode> {
if (typeof tag === 'string') {
var Ctor;
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
// If the label is reserved
if (config.isReservedTag(tag)) {
/ / create a vnode
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined.undefined, context
);
// If data does not exist and components does exist, it is a component
} else if((! data || ! data.pre) && isDef(Ctor = resolveAsset(context.$options,'components', tag))) {
// component
// Create a child component
vnode = createComponent(Ctor, data, context, children, tag);
} else {
vnode = new VNode(
tag, data, children,
undefined.undefined, context ); }}else{ vnode = createComponent(tag, data, context, children); }}Copy the code
This method is very important to create the core method of Vnode, remove the judgment code, the core is the above logic
tag
There are three cases of strings- Reserve label creation directly
Vnode
- Exists on instance
components
And,tag
Can be incomponents
Is found in the component - Unknown label Creation
Vnode
- Reserve label creation directly
tag
Instead of a string, create a component
The resolveAsset(context.$options, ‘components’, tag) method checks if $options.components contains a tag and returns it. Obviously we can find this tag in components because we declared it via Vue. Then we go to the createComponent(Ctor, data, Context, Children, Tag) method. Let’s look at the arguments first. Ctor is the constructor. Data is undefined, Content is the current instance, children is undefined, and Tag is comp-a.
createComponent
export function createComponent (
Ctor: Class<Component> | Function | Object | void, data: ? VNodeData, context: Component, children: ?Array<VNode>, tag? : string) :VNode | Array<VNode> | void {
data = data || {}
// install component management hooks onto the placeholder node
installComponentHooks(data)
// return a placeholder vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? ` -${name}` : ' '}`,
data, undefined.undefined.undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
Copy the code
In this method, we do some initialization of the options, mainly in the installComponentHooks method. Let’s go into this method.
const componentVNodeHooks = {
init() {},
prepatch() {},
insert() {},
destory(){}}const hooksToMerge = Object.keys(componentVNodeHooks)
function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
const existing = hooks[key]
const toMerge = componentVNodeHooks[key]
if(existing ! == toMerge && ! (existing && existing._merged)) { hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge } } }Copy the code
Its main job is to put the methods in componentVNodeHooks into the hooks of Data, and if there are methods with the same name, merge them using the mergeHook strategy. The merge strategy is
(init1, init2) => {
init1(),
init2()
}
Copy the code
After all the work is done, we will start new Vnode. Instantiating a Vnode is very simple, just creating a lot of attributes. The key thing to remember is that the tag of Vnode is vue-component-1-comp-a.
vm._update
Create the parent component Vnode, you can try to debug it yourself, the process is similar. Here we’ll look directly at the update process and take a look at the formation of the incoming Vnode
vnode: {
tag: 'div'.data: {
attrs: {id: 'app'}},children: [
Vnode // This is a div node
Vnode // Empty text node
Vnode // vue-component-1-comp-a]}Copy the code
Then we look at the _update method, which is in instance/ lifrCycle.js
Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
// Store the current VM instance
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
if(! prevVnode) {// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)}else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
// activeIntance becomes the previous instance
restoreActiveInstance()
}
Copy the code
PrevVnode does not exist so call vm. __Patch__ (vm.$el, vnode, hydrating, false /* removeOnly */). Remember that the parameter vm.$el passed in is our
. Vnode is its virtual node.
Patch method
__patch__ is what, let’s come to the platform/runtime/index, js, with such a definition Vue. Prototype. __patch__ = inBrowser? Patch: noop, so on the browser side it is the patch method. Continue looking for the patch method, which is defined in patch.js in the same directory. It is the return value of a method, createPatchFunction, that passes in an object defined in core/ vDOM /patch, So the return value method of the createPatchFunction method is the method we will execute.
vue
Why would you do that?
Let’s look at the incoming object of the createPatchFunction method
- NodeOps defines a lot of primitivity
node
Operation method - Modules include
vue
Custom properties and browser properties
In other words, Vue uses higher-order functions to separate the code, which is a good way to program. We should learn
Ok, patch, which I’m not going to post code on, is very long. We’re going to do it in a literal, pseudo-code kind of way
- if
vnode
undefinedoldvnode
Defined, executeinvokeDestroyHook(oldVnode)
- return
- if
oldVnode
There is no- perform
createElm(vnode, insertedVnodeQueue)
- perform
oldVnode
There are- In the first place to judge
oldVnode
Is it a real node oldvnode
Not really node, andsameVnode(oldVnode, vnode)
- perform
patchVnode
- perform
oldVnode
It’s a real node that does a bunch of things
- In the first place to judge
Roughly this judgment is enough, execute to the next level, the previous level will not be executed, look at the source code. So in our case, it jumps to oldvNode as the real node. Moving on, vue first creates an empty node on the real oldVnode as a virtual node, and then calls createElm, which is passed in as a VNode.
CreateElm Creates a node
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
/ / component vnode
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
// Create child nodes recursively
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
/ / createhook execution
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// Insert the real node
insert(parentElm, vnode.elm, refElm)
}
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null.true, children, i)
}
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
}
}
Copy the code
In createElm, first vue creates the element node of vNode, literally. CreateChildren is then called, passing in vNode and children, and createElm is called again, which becomes children[I]. Then it is obvious that when vNode is COMP-A, we will perform the actual component creation.
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)}if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true}}}Copy the code
We already know that vnode.data contains the initialization function we need, so we will execute it here. When we reach I (vnode, false /* hydrating */), we will execute vnode.init. There are three main steps in the init method
- perform
prepatch
thishook
- Initialize the child component instance
$mount
Mount child components
Once executed, we have vnode.componentInstance. And then there’s the initComponent and in this method it does the invokeCreateHooks which are the create methods in modules that are dom dependent. AppendChild (elm) nodeops.appendChild (parent, elm)
What is rendering at this point?
Take a look at the element. Yes, the station node comp-a. So when do you render the real nodes?
<div id="app">
<div>This is a div</div>
<comp-a></comp-a>
</div>
Copy the code
Back to createElm, createChildren is finished. So we’re done recursively creating children. Look at the code
// Create child nodes recursively
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
/ / createhook execution
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// Insert the real node
insert(parentElm, vnode.elm, refElm)
Copy the code
The parent component’s invokeCreateHooks method, which is also a set of hook functions, are then executed for the actual insert. At this point we have vnode.elm, which contains the children node. Look at the element after execution
<body>
<div id="app">
<div>This is a div</div>
<comp-a></comp-a>
</div>
<div id="app">
<div>This is a div</div>
<div>This is a child component</div>
</div>
</body>
Copy the code
Yes, it’s rendered, but there are two, it’s easy, just delete the last one. Obviously after createElm is over, we’re going back to Patch
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0.0)}else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
Copy the code
To delete the node, perform insert. This insert is a component of vNode. data, where we will execute the component mounted hook and set instance Mounted to end. The mounted hook function of the vue instance is executed.
Local registration
Let’s look at what’s different about local registration.
<div id="app">
<div>This is a div</div>
<comp-a></comp-a>
</div>
<script>
const componentA = {
template: '
this is a child component
'
}
const vm = new Vue({
el: '#app'.components: {
'comp-a': componentA
}
})
</script>
Copy the code
ResolveAsset (context.$options, ‘components’, tag); vm.$options (context.$options, ‘components’, tag); So when we run the createComponent method, we’ll run this code
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
Copy the code
When we execute the extend method, we pass options with no name, only Vue. Extend. So this code doesn’t execute either
if (name) {
Sub.options.components[name] = Sub
}
Copy the code
There is no difference later, you can go through the process of render to patch as I did above.