Vue自定义指令介绍及原理
Vue自定义指令Vue指令:在使用Vue框架进行前端开发时,我们经常会使用一些特殊指令来快速实现一些效果或功能。常见指令如:v-bind、v-if (v-else)、v-show、v-html等等都是一些比较常用的指令由于本文主要介绍自定义指令相关的一些知识,所以对于Vue自带指令就不做过多赘述了在这情况下,Vue官方也推出了一种编写自定义指令的方法。我们可以定义开发我们自定义的模版指令,来对一些
Vue自定义指令
Vue指令:
在使用Vue框架进行前端开发时,我们经常会使用一些特殊指令来快速实现一些效果或功能。
常见指令如:v-bind、v-if (v-else)、v-show、v-html等等都是一些比较常用的指令
使用如下:
<span v-if=true v-html="htmlContext || '--'"></span>
<span v-show=true> show html </span>
由于本文主要介绍自定义指令相关的一些知识,所以对于Vue自带指令就不做过多赘述了
在这情况下,Vue官方也推出了一种编写自定义指令的方法。我们可以定义开发我们自定义的模版指令,来对一些特殊的需求效果功能进行开发与实现
在Vue的官方文档中是如下描述的:
自定义指令不只支持全局注册,也支持在组件中进行注册并引用,具体操作在文档中都有详细描述
Vue自定义指令
Vue自定义指令机制包含五个钩子函数,每个钩子函数包含四个回调参数
钩子函数简单来说可以称作为生命周期,如果对生命周期有过了解或应用应该会比较容易理解
Vue2钩子函数:
bind:只调用一次,组件初始化时会调用该钩子函数
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。可以通过比较更新前后的值来忽略不必要的模板更新
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
unbind:只调用一次,指令与元素解绑时调用
Vue3钩子函数:
setup() - 新增,对应之前的 Vue2 的 created、beforeCreate。
onBeforeMount - 组件挂载到节点之前执行
onMounted - 组件挂载完成后执行
onBeforeUpdate:组件更新之前执行
onUpdated - 组件更新完成之后执行
onBeforeUnmount - 组件卸载之前执行
onUnmounted:组件卸载完成后执行
特殊:(onActivated、onDeactivated、onErrorCaptured)
onActivated: keep-alive中的组件,会多出两个生命周期钩子函数,被激活时执行
onDeactivated: 组件切换时执行,当前组件消亡时执行
onErrorCaptured: 捕获子组件、孙组件异常时被调用执行
四个函数的回调参数:el、binding、vnode、oldVnode
el:指令所绑定的元素
binding(对象):
name(指令名称)、value(指令的绑定值)、oldValue(指令绑定的前一个值)、
expression(绑定指令的表达式)、arg(绑定的指令参数)、
modifiers(一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true })
vnode:即Vue虚拟DOM生成的虚拟节点,细节可学习Vue虚拟DOM原理
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子函数中可用
接下来上一个简单的组件注册自定义指令的例子:
效果为一渲染input元素后即添加焦点,可辅助进行表单校验
// html
<input v-focus>
// JS
directives: {
focus: {
// 指令的定义
inserted: function (el,binding,vnode,oldVnode) {
el.focus();
// 展示回调值输出
console.log('el',el);
console.log('binding',binding);
console.log('vnode',vnode);
console.log('oldVnode',oldVnode);
}
}
}
输出如下:
Vue自定义指令原理:
以上介绍了Vue自定义指令的相关介绍以及用法
接下来将集中分享一下关于Vue自定义指令的实现原理
首先我们知道,
Vue一个机制首先要在 vm.$options 上进行挂载 => 而后进行正则解析处理 => 最后生成虚拟dom
这个的流程就不在此赘述了,有时间会有集中出一篇关于这方面的文章
接下来我们直接分析patchDom的过程
源码链接:Vue自定义指令源码 Github
为了页面不占用太大篇幅,源码就不全额摘抄了,下面将集中分析部分关键原理
/* @flow */
import { emptyNode } from 'core/vdom/patch'
import { resolveAsset, handleError } from 'core/util/index'
import { mergeVNodeHook } from 'core/vdom/helpers/index'
export default {
create: updateDirectives,
update: updateDirectives,
destroy: function unbindDirectives (vnode: VNodeWithData) {
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)
// inserted 需要触发钩子函数的元素列表
const dirsWithInsert = []
// componentUpdated
const dirsWithPostpatch = []
// 更新新旧节点 指令
let key, oldDir, dir
for (key in newDirs) {
oldDir = oldDirs[key]
dir = newDirs[key]
// 不存在旧节点时,直接插入!
if (!oldDir) {
// new directive, bind
callHook(dir, 'bind', vnode, oldVnode)
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir)
}
// 存在旧节点时:更新
} else {
// existing directive, update
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) {
const callInsert = () => {
for (let i = 0; i < dirsWithInsert.length; i++) {
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
}
}
// 判断是否为新创建的节点,如果是,则使用该方法将钩子函数合并,并推迟执行
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]) {
// 执行指令的unbind方法
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
}
}
}
}
const emptyModifiers = Object.create(null)
function normalizeDirectives (
dirs: ?Array<VNodeDirective>,
vm: Component
): { [key: string]: VNodeDirective } {
const res = Object.create(null)
if (!dirs) {
// $flow-disable-line
return res
}
let i, dir
for (i = 0; i < dirs.length; i++) {
dir = dirs[i]
if (!dir.modifiers) {
// $flow-disable-line
dir.modifiers = emptyModifiers
}
res[getRawDirName(dir)] = dir
dir.def = resolveAsset(vm.$options, 'directives', dir.name, true)
}
// $flow-disable-line
return res
}
// 获取指令name
function getRawDirName (dir: VNodeDirective): string {
return dir.rawName || `${dir.name}.${Object.keys(dir.modifiers || {}).join('.')}`
}
// 用于循环调用钩子函数
function callHook (dir, hook, vnode, oldVnode, isDestroy) {
const fn = dir.def && dir.def[hook]
if (fn) {
try {
fn(vnode.elm, dir, vnode, oldVnode, isDestroy)
} catch (e) {
handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`)
}
}
}
更多推荐
所有评论(0)