(juejin. Cn/post / 705288… “Juejin. Cn/post / 705288…
Previously on & Background
We have simplified createPatchFunction and the patch function it returns, leaving only the code that can express the initial rendering process as follows:
Patch calls createElm to turn the VNode tree into a real DOM tree and insert it into the body. CreateElm creates native HTML elements and custom components.
Since rendering a custom component is a big job, this article will talk about the rendering process of custom components.
Note: The rendering of this custom component is not independent, it is a branch of the initial rendering process. Why do you say so? Take this template as an example:
, first render from the div virtual node with id app, and then render some-com when it renders its children. CreateElem is now ready to handle the custom components. So the initial rendering of the root instance does not conflict with the initial rendering of the component.
Second, the createElm
SRC /core/vdom/patch.js -> function createPatchFunction internal method
Method parameters:
vnode
: virtual node instance object, called earlier on the first renderingvm._render()
Get the entire virtual DOM treeinsertedVnodeQueue
, node queue to be insertedparentElm
, the parent node, when thevnode
Become a realdom
Insert into the parent noderefElm
The: reference element is the sibling of div#app, and if it has a value, the DOM rendered by vnode is inserted in front of itnested
, whether nestedowernArray
, owner arrayindex
Index,
Method creates native HTML elements and custom components.
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) { vnode.isRootInsert = ! nested// Here's the thing:
// This createComponent is responsible for handling cases where vNode is a custom component
// If vNode is a normal element, createComponent returns false
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
// If vNode is a custom component, createComponent returns true and terminates
return
}
// VNode is a normal element
// Get the data object
const data = vnode.data
// Get the list of child nodes
const children = vnode.children
// Label name of a vnode
const tag = vnode.tag
if (isDef(tag)) {
// Create a new node and mount it to the vNode object.
Vnode. elm is a real DOM element
vnode.elm = vnode.ns // ns is the namespace, ignore it
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode) // Let's study the case
if (__WEEX__) {
} else {
// Recursively create all child nodes (common elements, components)
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
/ / call createHooks
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// Insert the node into the parent node for the first rendering, which is a crucial step,
// vnode.elm is the real element created, and there is a whole DOM tree containing all the template content,
ParentElm is the body element
// Insert the DOM element into the body to render
insert(parentElm, vnode.elm, refElm)
}
} else if (isTrue(vnode.isComment)) {
// The vnode.tag attribute does not exist, that is, it is not an element or custom component
Create a comment node and insert the parent node
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
// Not a comment, not an element, just text processing
// Text node, create text node and insert parent node
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
Copy the code
2.1 the createComponent
SRC /core/vdom/patch.js -> function createPatchFunction internal method
Method parameters:
vnode
: Node objectinsertedVnodeQueue
, list of nodes to be insertedparentElm
The parent elementrefElm
.ref
With reference to the element
Methods:
- if
vnode
Is a component, then executesinit
Hook to create and mount the component instance - Then execute the individual module’s for the component
create
hook - If the component is
keep-alive
Package, then activate the component - Returns if it is a custom component
true
If it is a normal HTML element and nothing is returnedundefined
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
// Get the vnode.data object
let i = vnode.data
if (isDef(i)) {
// Check if the component instance already exists && is wrapped by
// Components wrapped by keep-alive are activated and deactivated. Normal components need to be created and destroyed
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
// Execute the vnode.data.hook. Init hook,
// This thing is generated with installComponentHooks when a VNode is generated
// If the component is wrapped by keep-alive:
// Execute the prepatch hook to update the oldVnode properties with the vNode properties
// If the component is not wrapped by keep-alive or rendered for the first time, initialize the component and enter the mount phase
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)}// After the data.hook. Init hook is called, if the vNode is a child component,
// At this point the child component instances should have been generated and mounted
// Set vnode.elm for the child component
if (isDef(vnode.componentInstance)) {
// If a vNode is a child component,
// A component instance is created and mounted after the data.hook.init hook is called
// Execute the create hook for each module to the component
initComponent(vnode, insertedVnodeQueue)
// Insert the component's DOM node into the parent node
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
// Activate the component if it is wrapped by keep-alive
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true}}}Copy the code
2.1.1 data. The hook. The init
Data is an object derived from the inline attributes and instructions of elements in the template. When creating a custom component VNode, the hook attribute will be added to vNode. data, which contains four hook methods: Init/prepatch/insert/destroy, this process is roughly process code sample is as follows:
export function createComponent () {
installComponentHooks(data);
}
// installComponentHooks
function installComponentHooks (data: VNodeData) {
// The data.hook object is initialized
const hooks = data.hook || (data.hook = {})
// Walk through the hooksToMerge array,
// hooksToMerge = Object.keys(componentVnodeHooks)
// hooksToMerge = ['init', 'prepatch', 'insert', 'destroy']
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
}
}
Copy the code
The hook methods are in the componentVnodeHooks object. They work like this:
- To deal with
vnode.componentInstance
Already exists and the component iskeep-alive
Package, then call directlyhook.prepatch
Enter thepatch
Because thekeep-alive
The component is not destroyed. There is no need to recreate the component instancepatch
Update render; - Another scenario involves creating component instances and mounting them to
vnode.componentInstance
On. When creating a component instanceConstructor of the new component
And thisThe constructor
Is in thecreateComponent
Through the componentThe options object
andVue.options
Merge, and then inheritVue
The resulting subclass:function VueComponent
; - After obtaining the instance, manually invoke the child component’s
$mount
Method, so that the child component into the mount phase; This is where it gets interesting. Subcomponents$mount
The child component template is then compiled (parse+generate
)Child components
theRender function
And then createChild components
theRender the watcher
,VNode
And then callSubcomponents. _update ()
. You can see that this is a recursive process, and if there are children, the cycle continues until all the components are mounted to the corresponding parent node.
const componentVNodeHooks = {
/ / initialization
init (vnode: VNodeWithData, hydrating: boolean): ? boolean {if (
vnode.componentInstance && // The component instance already exists! vnode.componentInstance._isDestroyed &&// The component instance was not destroyed
vnode.data.keepAlive // The component is in a keep-alive package
) {
// The init process of a keep-alive component is prepatch
const mountedNode: any = vnode
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
// Common component creation process:
// Execute this, and you get vnode.componentInstance,
// That is, the instance created by the component constructor
// The constructor of this instance is a subclass extending Vue, inheriting Vue's capabilities
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// Execute the component's $mount method to manually mount it
// Enter the mount phase, the next step is to get the render function from the compiler,
// Then create render watcher then go to components mount, patch
// This path continues until the component is mounted to the parent node
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
// Update the VNode. Configure the updated VNode with the new VNode
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
},
// Executes the component's Mounted lifecycle hook
insert (vnode: MountedComponentVNode) {
},
// Destroy component:
destroy (vnode: MountedComponentVNode) {
}
}
Copy the code
2.1.2 data. Hook. Prepatch
Prepatch is more about expressing the rendering process of diff + patch after the change of responsive data, which is not expanded for the time being
const componentVNodeHooks = {
/ / initialization
init (vnode: VNodeWithData, hydrating: boolean): ? boolean {},// Update VNode, update old VNode with new VNode configuration
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
// New VNode, with the new VNode to configure the various properties of the updated VNode
const options = vnode.componentOptions
// Component instance of the old VNode component
const child = vnode.componentInstance = oldVnode.componentInstance
Update various attributes on child with attributes on vNode
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
)
},
insert (vnode: MountedComponentVNode) {},
destroy (vnode: MountedComponentVNode) {}
}
Copy the code
2.1.3 createComponentInstanceForVnode
Methods location: SRC/core/vdom/create – component. Js – > function createComponentInstanceForVnode
Method parameters:
vnode
, virtual nodeparent
The parent element
Method Function: to
export function createComponentInstanceForVnode (
vnode: any,
parent: any // The currently active parent instance, such as
, is the root instance
) :Component {
const options: InternalComponentOptions = {
_isComponent: true.// Identifies the current instance as a component
_parentVnode: vnode, // The parent node of the current component is vnode
parent
}
// Check the render function of the inline template, if it is an inline template,
// Replace the render function with the inline template render function
// Inline template, see vue official documentation
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
/ / vnode.com ponentOptions Ctor is generated when vnode extension Vue of subclasses
// Each custom component has its own subclass constructor
return new vnode.componentOptions.Ctor(options)
}
Copy the code
2.1.4 initComponent
Initialize the component. This is done in the invokeCreateHooks method. Note that the create is not a component’s Created lifecycle hook. But attributes, style, and the name of the class, instructions, ref (attrs/stle/klass/directives/ref) cycle method, These methods are in the Modules option passed in when createPatchFunction({nodeOps, modules}) is executed;
In the official document of Vue, there is a periodic function to introduce instructions, which is called hook;
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
vnode.data.pendingInsert = null
}
vnode.elm = vnode.componentInstance.$el
if (isPatchable(vnode)) {
// For the first rendering, call the create hook method of modules;
// Note that this is not a Created life cycle for the component, but rather for the ATTr, Klass (class name), style, and directives properties
// For maintenance, here is an example of the directive directives directives hook function:
// https://cn.vuejs.org/v2/guide/custom-directive.html#%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
} else {
// empty component root.
// skip all element-related modules except for ref (#3455)
registerRef(vnode)
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode)
}
}
Copy the code
2.2 nodeOps createElement method
For example, nodeOps encapsulates the browser’S DOM API. CreateElement is the method to create an element, which is a real DOM element.
// Create an element node with the tag tagName
export function createElement (tagName: string, vnode: VNode) :Element {
// Create the element node
const elm = document.createElement(tagName)
if(tagName ! = ='select') {
return elm
}
// If the select element, set the multiple attribute for it
if(vnode.data && vnode.data.attrs && vnode.data.attrs.multiple ! = =undefined) {
elm.setAttribute('multiple'.'multiple')}return elm
}
Copy the code
2.3 createChildren
SRC /core/vdom/patch.js -> function createPatchFunction internal method
Method parameters:
vnode
, virtual node listchildren
.vnode.children
List, that is, the child node list of the current virtual node.insertedVnodeQueue
, the list of nodes to be inserted
Create an element from vnode.children recursively and insert it into the parent element (vnode.elm).
// Create all child nodes and insert the child nodes into the parent node to form a DOM tree
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
// children is an array that identifies a set of nodes
if(process.env.NODE_ENV ! = ='production') {
// Check whether the key of this group of nodes is duplicated
checkDuplicateKeys(children)
}
// Iterate through the list of child nodes, recursively create these nodes by calling createElm,
// Then insert the parent node to form a DOM tree
for (let i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null.true, children, i)
}
} else if (isPrimitive(vnode.text)) {
// Text node, create text node and insert parent node
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
}
}
Copy the code
2.4 invokeCreateHooks
Call the CREATE methods of each module, such as ATTRs, style, and directives, and then execute the mounted lifecycle methods of the component. These modules are modules passed in when createPatchFunction({nodeOps, modules});
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode)
// Invoke the component's data.hook. Insert hook to execute the component's Mounted lifecycle method
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
Copy the code
Insert hook is a vNode.data.hook that installs Componenthooks on data.hook when creating a VNode
Against 2.4.1 data. The hook. The insert
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ? boolean {},// Update the VNode. Configure the updated VNode with the new VNode
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {},
// Executes the component's Mounted lifecycle hook
insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
// If the component is not mounted, the component's Mounted lifecycle hook is called
if(! componentInstance._isMounted) { componentInstance._isMounted =true
callHook(componentInstance, 'mounted')}// Handle the keep-alive exception....
},
// Destroy component:
destroy (vnode: MountedComponentVNode) {}
}
Copy the code
2.5 insert
SRC /core/vdom/patch.js -> function createPatchFunction internal method
Method parameters:
parent
The parent node,elm
The element to be inserted into the parent node queueref
, reference node, yeselm
The younger nodes ensure the order of insertion of the later nodes
Method: Insert ELM into the parent child node queue. After performing this step, the VNode becomes a real DOM.
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (nodeOps.parentNode(ref) === parent) {
// is inserted before the ref reference node, so ref is the younger node of elm
nodeOps.insertBefore(parent, elm, ref)
}
} else {
// Appends to the end of the child node of parent
nodeOps.appendChild(parent, elm)
}
}
}
Copy the code
Third, summary
3.1 Summary
This article discussed in detail the logic of the createElm method, which creates real elements based on VNode and contains two scenarios:
-
If a VNode is a custom component, the createComponent method is called. Data.hook. init (Vue. Prototype. _init) is called internally to instantiate the child component. Then call $mount of the subcomponent to start compiling and mounting the subcomponent, and complete the rendering of the subcomponent; In the process of rendering sub-components, the initial rendering logic of patch function about sub-components will be triggered.
-
If it is a normal element, the native HTML element is created with nodeOps. CreateElement and the processing of its children is a recursive call to the createElm method.
-
At the end of the createElm method, the vNode. Elm is inserted into parentElm, where parentElm is the body element.
3.2 Mounting Stage Summary
This was followed by a series of steps to remove placeholder nodes until vue.prototype.$mount implemented the stack and Vue’s initial rendering was completed.
$mount = new Vue; $mount = new Vue; $mount = new Vue;
New Vue() -> Vue.prototype._init() -> Vue. Prototype.$mount() -> compileToFunctions(template...) Mount () -> mountComponent() -> new Watcher(updateComponent) -> updateComponent -> Vue.prototype._render() -> vue.prototype. _update() -> vue.prototype. __patch__() -> createPatchFunction Patch () -> createElm() -> insert(parentElm, vnode.elm)Copy the code