directive
定义:本质就是一个 JavaScript 对象,对象上挂着一些钩子函数。
实现:在元素的生命周期中注入代码。
指令注册
注册原理:把指令的定义保存到相应的地方,未来使用的时候可以从保存的地方拿到。
全局注册与局部注册的区别:
- 全局注册存放在
instance.appContext里 - 局部注册存放在组件对象里
/**
* 全局注册 app.directive
*/
function createApp(rootComponent, rootProps = null) {
const context = createAppContext()
const app = {
_component: rootComponent,
_props: rootProps,
directive(name, directive) {
// 检测指令名是否与内置的指令名有冲突
if ((process.env.NODE_ENV !== 'production')) {
validateDirectiveName(name)
}
// 没有第二个参数,则获取对应的指令对象
if (!directive) {
return context.directives[name]
}
// 重复注册的警告
if ((process.env.NODE_ENV !== 'production') && context.directives[name]) {
warn(/* ... */)
}
context.directives[name] = directive
return app
}
}
return app
}
指令解析
const DIRECTIVES = 'directives';
/**
* 解析指令 - 根据指令名称找到保存的指令的对象
*/
function resolveDirective(name) {
return resolveAsset(DIRECTIVES, name)
}
/**
* 解析资源
*/
function resolveAsset(type, name, warnMissing = true) {
// 获取当前渲染实例
const instance = currentRenderingInstance || currentInstance
if (instance) {
const Component = instance.type
// 先通过 resolve 函数解析局部注册的资源,如果没有则解析全局注册的资源
const res = resolve(Component[type], name) || resolve(instance.appContext[type], name)
// 如果没有,则在非生产环境下报警告
if ((process.env.NODE_ENV !== 'production') && warnMissing && !res) {
warn(/* ... */)
}
return res
} else if ((process.env.NODE_ENV !== 'production')) {
warn(/* ... */)
}
}
/**
* 解析
*/
function resolve(registry, name) {
// 先根据 name 匹配,如果失败则把 name 转换成驼峰格式继续匹配,如果失败则把 name 变为驼峰式后再首字母大写继续匹配
return (registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]))
}
/**
* 绑定指令 - 给 vnode 添加一个 dirs 属性,值为这个元素上所有指令构成的数组
*/
function withDirectives(vnode, directives) {
const internalInstance = currentRenderingInstance
if (internalInstance === null) {
(process.env.NODE_ENV !== 'production') && warn(/* ... */)
return vnode
}
const instance = internalInstance.proxy
const bindings = vnode.dirs || (vnode.dirs = [])
// 遍历 directives,拿到每一个指令对象以及指令的相关内容
for (let i = 0; i < directives.length; i++) {
let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
if (isFunction(dir)) {
dir = {
mounted: dir,
updated: dir
}
}
bindings.push({
dir, // 指令
instance, // 组件实例
value, // 指令值
oldValue: void 0,
arg, // 参数
modifiers // 修饰符
})
}
return vnode
}
指令生命周期
挂载
/**
* 挂载元素
*/
const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
let el
const { type, props, shapeFlag, dirs } = vnode
// 创建 DOM 元素节点
el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is)
if (props) {
// 处理 props,比如 class、style、event 等属性
}
// 处理子节点是纯文本的情况
if (shapeFlag & 8/*TEXT_CHILDREN */) {
hostSetElementText(el, vnode.children)
}
// 处理子节点是数组的情况,挂载子节点
else if (shapeFlag & 16 /* ARRAY_CHILDREN*/) {
mountChildren(vnode.children, el, null, parentComponent, parentSuspense, isSVG && type !== 'foreignObject', optimized || !!vnode.dynamicChildren)
// 在元素插入到容器之前会执行指令的 beforeMount 钩子函数
if (dirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
}
// 把创建的 DOM 元素节点挂载到 container 上
hostInsert(el, container, anchor)
// 在元素插入到容器之后会执行指令的 mounted 钩子函数
if (dirs) {
// 通过 queuePostRenderEffect 进行包装,目的是组件 render 后同步执行 mounted 钩子
queuePostRenderEffect(() => {
invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
})
}
}
}
/**
* 执行指令 hook
* @param {Object} vnode - 新 vnode
* @param {Object} prevVNode - 旧 vnode
* @param {Object} instance - 组件实例
* @param {string} name - 钩子名称
*/
function invokeDirectiveHook(vnode, prevVNode, instance, name) {
const bindings = vnode.dirs
const oldBindings = prevVNode && prevVNode.dirs
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i]
if (oldBindings) {
binding.oldValue = oldBindings[i].value
}
const hook = binding.dir[name]
if (hook) {
callWithAsyncErrorHandling(hook, instance, 8/* DIRECTIVE_HOOK */, [vnode.el, binding, vnode, prevVNode])
}
}
}
更新
/**
* 组件更新
*/
const patchElement = (nl, n2, parentComponent, parentSuspense, isSVG, optimized) => {
const el = (n2.el = nl.el)
const oldProps = (n1 && n1.props) || EMPTY_OBJ
const newProps = n2.props || EMPTY_OBJ
const { dirs } = n2
// 更新 props
patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG)
const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
// 在更新子节点之前会执行指令的 beforeUpdate 钩子
if (dirs) {
invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
}
//更新子节点
patchChildren(n1, n2, el, null, parentComponent, parentSuspense, areChildrenSVG)
// 在更新子节点之后会执行指令的 updated 钩子
if (dirs) {
queuePostRenderEffect(() => {
invokeDirectiveHook(vnode, null, parentComponent, 'updated')
})
}
}
卸载
/**
* 组件卸载
*/
const unmount = (vnode, parentComponent, parentSuspense, doRemove = false) => {
const { type, props, children, dynamicChildren, shapeFlag, patchFlag, dirs } = vnode
let vnodeHook
if ((vnodeHook = props && props.onVnodeBeforeUnmount)) {
invokeVNodeHook(vnodeHook, parentComponent, vnode)
}
const shouldInvokeDirs = shapeFlag & 1/* ELEMENT */ && dirs
if (shapeFlag & 6/* COMPONENT*/) {
unmountComponent(vnode.component, parentSuspense, doRemove)
} else {
if (shapeFlag & 128/* SUSPENSE */) {
vnode.suspense.unmount(parentSuspense, doRemove)
return
}
// 在移除元素的子节点之前执行指令的 beforeUnmount 钩子
if (shouldInvokeDirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount')
}
// 卸载子节点,递归卸载自身节点和子节点
if (dynamicChildren && (type !== Fragment || (patchFlag > 0 && patchFlag & 64/* STABLE_FRAGMENT */))) {
unmountChildren(dynamicChildren, parentComponent, parentSuspense)
} else if (shapeFlag & 16/* ARRAY_CHILDREN */) {
unmountChildren(children, parentComponent, parentSuspense)
}
if (shapeFlag & 64/* TELEPORT */) {
vnode.type.remove(vnode, internals)
}
// 移除自身节点
if (doRemove) {
remove(vnode)
}
}
if ((vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs) {
queuePostRenderEffect(() => {
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
// 在移除元素的子节点之后执行指令的 unmounted 钩子
if (shouldInvokeDirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'unmounted')
}
}, parentSuspense)
}
}