preface
Custom directives are the second most commonly used component in vUE, with bind, INSERTED, Update, componentUpdated, and unbind lifecycle hooks. This article will introduce the working principle of VUE instruction. From this article, you will get:
- How instructions work
- Note on the use of instructions
The basic use
Official website case:
<div id='app'>
<input type="text" v-model="inputValue" v-focus>
</div>
<script>
Vue.directive('focus', {
// called the first time the element is bound
bind () {
console.log('bind')},// When the bound element is inserted into the DOM...
inserted: function (el) {
console.log('inserted')
el.focus()
},
// Called when the component VNode is updated
update () {
console.log('update')},// call after the component's VNode and its child VNodes are all updated
componentUpdated () {
console.log('componentUpdated')},// Only called once, when the instruction is unbound from the element
unbind () {
console.log('unbind')}})new Vue({
data: {
inputValue: ' '
}
}).$mount('#app')
</script>
Copy the code
Principle of instruction
Initialize the
Initializing global API, under the platforms/web, call createPatchFunction generated VNode into real DOM patch method, comparatively important step is to define the initialization and DOM node corresponding hooks method, In the DOM, create, avtivate, Update, remove, and destroy are polled to invoke the corresponding hooks, some of which are entrances to the directive declaration cycle.
// src/core/vdom/patch.js
const hooks = ['create'.'activate'.'update'.'remove'.'destroy']
export function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
// modules correspond to vue modules, including class, style, domListener, domProps, attrs, directive, ref, and transition
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
// Finally convert hooks to {hookEvent: [cb1, cb2... . } form
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
/ /...
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// ...}}Copy the code
Copy the code
Template compilation
Template compilation parses the parameters of the directive, and the ASTElement looks like this:
{
tag: 'input'.parent: ASTElement,
directives: [{arg: null./ / parameters
end: 56.// Command end character position
isDynamicArg: false.// Dynamic parameter,v-xxx[dynamicParams]=' XXX 'form call
modifiers: undefined.// Directive modifiers
name: "model".rawName: "v-model".// Directive name
start: 36.// Command start character position
value: "inputValue" / / template
},
{
arg: null.end: 67.isDynamicArg: false.modifiers: undefined.name: "focus".rawName: "v-focus".start: 57.value: ""}].// ...
}
Copy the code
Generate render method
Vue recommends using instructions to operate DOM. Since custom instructions may modify DOM or attributes, the influence of instructions on template parsing is avoided. When generating a rendering method, instructions such as V-Model are processed first, which is essentially a syntax sugar. The element is assigned a value attribute and an input event (in the case of input, this can also be customized).
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('input', {
directives: [{
name: "model".rawName: "v-model".value: (inputValue),
expression: "inputValue"
}, {
name: "focus".rawName: "v-focus"}].attrs: {
"type": "text"
},
domProps: {
"value": (inputValue) // Attributes added when processing v-model directives
},
on: {
"input": function($event) { // A custom event added while processing the V-model directive
if ($event.target.composing)
return;
inputValue = $event.target.value
}
}
})])
}
Copy the code
Generate VNode
The instructions for vUE are designed so that we can manipulate the DOM easily and do no extra processing when generating vNodes.
Generating a real DOM
During vUE initialization, we need to keep two things in mind:
- Initializations of states are parent -> child, as in
beforeCreate
,created
,beforeMount
The call order is parent -> child - real
DOM
The mount order is child -> parent, as inmounted
This is because reality is being generatedDOM
Process, if encountered component, will go through the process of component creation, trueDOM
The generation of is a hierarchical splicing from child to parent.
When createElm is used to create a real DOM, it checks whether VNode has a data attribute. If it does, it calls invokeCreateHooks.
// src/core/vdom/patch.js
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
// ...
CreateComponent has a return value, which is the method that created the component. If it does not return a value, proceed to the following method
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
/ /...
if (isDef(data)) {
// After the real node is created, update node properties, including instructions
// The directive first calls the bind method and then initializes the subsequent hooks methods of the directive
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// Insert from bottom up
insert(parentElm, vnode.elm, refElm)
// ...
}
Copy the code
That’s the first entry to the directive hook method. It’s time to unmask directive.js. The core code is as follows:
// src/core/vdom/modules/directives.js
// By default, both are updateDirectives
export default {
create: updateDirectives,
update: updateDirectives,
destroy: function unbindDirectives (vnode: VNodeWithData) {
Vnode === emptyNode when destroyed
updateDirectives(vnode, emptyNode)
}
}
function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode)
}
}
function _update (oldVnode, vnode) {
const isCreate = oldVnode === emptyNode
const isDestroy = vnode === emptyNode
const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)
const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
// Callback after insertion
const dirsWithInsert = [
// Callback after the update is complete
const dirsWithPostpatch = []
let key, oldDir, dir
for (key in newDirs) {
oldDir = oldDirs[key]
dir = newDirs[key]
// New element directive, which executes a INSERTED hook method
if(! oldDir) {// new directive, bind
callHook(dir, 'bind', vnode, oldVnode)
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir)
}
} else {
// existing directive, update
// The element already exists, the componentUpdated hook method is executed once
dir.oldValue = oldDir.value
dir.oldArg = oldDir.arg
callHook(dir, 'update', vnode, oldVnode)
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir)
}
}
}
if (dirsWithInsert.length) {
// When the real DOM is inserted into the page, this callback method is called
const callInsert = () = > {
for (let i = 0; i < dirsWithInsert.length; i++) {
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
}
}
// VNode merges insert hooks
if (isCreate) {
mergeVNodeHook(vnode, 'insert', callInsert)
} else {
callInsert()
}
}
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch'.() = > {
for (let i = 0; i < dirsWithPostpatch.length; i++) {
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
}
})
}
if(! isCreate) {for (key in oldDirs) {
if(! newDirs[key]) {// no longer present, unbind
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
}
}
}
}
Copy the code
For the first time, perform the following operations:
oldVnode === emptyNode
.isCreate
fortrue
Call all elements in the current elementbind
Hook method.- Check for presence in instruction
inserted
Hook, if present, willinsert
Hook merge intoVNode.data.hooks
Attribute. DOM
The command is executed after the mount is completeinvokeInsertHook
, all mounted nodes, ifVNode.data.hooks
ininsert
Hook. Is called, at which point the instruction binding is triggeredinserted
Methods.
Normally the first creation only takes the BIND and INSERTED methods, whereas update and componentUpdated correspond to bind and INSERTED. When the component dependency state changes, VNode DIff algorithm is used to patch the node. The call flow is as follows:
- Reactive data changes, call
dep.notify
To notify data updates. - call
patchVNode
For old and newVNode
Differentiated update, and full update currentVNode
Attributes, including directives, enterupdateDirectives
Methods). - If the instruction exists
update
Hook method, calledupdate
Hook method and initializecomponentUpdated
The callback will bepostpatch hooks
Mount to theVNode.data.hooks
In the. - Triggered when the current node and its child nodes are updated
postpatch hooks
, i.e., of instructionscomponentUpdated
methods
The core code is as follows:
// src/core/vdom/patch.js
function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
// ...
const oldCh = oldVnode.children
const ch = vnode.children
// Update all attributes of the node
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
// ...
if (isDef(data)) {
// Call the postPatch hook
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
Copy the code
The unbind method calls invokeDestroyHook when the node is destroyed, which is not described here.
Matters needing attention
While I pass the parameter (V-xxx =’param’) as a reference type, NO bind or INSERTED instruction can be triggered when the data changes, because within the declaration period of the instruction, Bind and INSERTED are called only once during initialization, and are followed only by Update and componentUpdated.
The directive declaration cycle is executed in the order bind -> INSERTED -> Update -> componentUpdated. If the directive depends on the content of a subcomponent, it is recommended to write the corresponding business logic in componentUpdated.
In VUE, many methods are called in a loop, such as hooks methods, event callbacks, etc. Generally, calls are wrapped with try catch. The purpose of this method is to prevent one method from failing and causing the whole program to crash, which can be used for reference in the development process.
summary
Began to look at the whole VUE source code, for many details of the method are not how to understand, by combing the implementation of each specific function, gradually can see the whole VUE panorama, but also to avoid the development of some pits in use.
GitHub