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] = directivereturn app}}return app
}
指令解析
const DIRECTIVES = 'directives';
/*** 解析指令 - 根据指令名称找到保存的指令的对象*/
function resolveDirective(name) {return resolveAsset(DIRECTIVES, name)
}/*** 解析资源*/
function resolveAsset(type, name, warnMissing = true) {// 获取当前渲染实例const instance = currentRenderingInstance || currentInstanceif (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 = currentRenderingInstanceif (internalInstance === null) {(process.env.NODE_ENV !== 'production') && warn(/* ... */)return vnode}const instance = internalInstance.proxyconst 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 elconst { 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.dirsconst oldBindings = prevVNode && prevVNode.dirsfor (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_OBJconst newProps = n2.props || EMPTY_OBJconst { dirs } = n2// 更新 propspatchProps(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 } = vnodelet vnodeHookif ((vnodeHook = props && props.onVnodeBeforeUnmount)) {invokeVNodeHook(vnodeHook, parentComponent, vnode)}const shouldInvokeDirs = shapeFlag & 1/* ELEMENT */ && dirsif (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)}
}