In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-21 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)05/31 Report--
本篇内容介绍了"Vue指令的实现原理是什么"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
一、基本使用
官网案例:
Vue.directive('focus', { // 第一次绑定元素时调用 bind () { console.log('bind') }, // 当被绑定的元素插入到 DOM 中时…… inserted: function (el) { console.log('inserted') el.focus() }, // 所在组件VNode发生更新时调用 update () { console.log('update') }, // 指令所在组件的 VNode 及其子 VNode 全部更新后调用 componentUpdated () { console.log('componentUpdated') }, // 只调用一次,指令与元素解绑时调用 unbind () { console.log('unbind') } }) new Vue({ data: { inputValue: '' } }).$mount('#app')二、指令工作原理2.1、初始化
初始化全局API时,在platforms/web下,调用createPatchFunction生成VNode转换为真实DOM的patch方法,初始化中比较重要一步是定义了与DOM节点相对应的hooks方法,在DOM的创建(create)、激活(avtivate)、更新(update)、移除(remove)、销毁(destroy)过程中,分别会轮询调用对应的hooks方法,这些hooks中一部分是指令声明周期的入口。
// src/core/vdom/patch.jsconst 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对应vue中模块,具体有class, style, domListener, domProps, attrs, directive, ref, transition for (j = 0; j < modules.length; ++j) { if (isDef(modules[j][hooks[i]])) { // 最终将hooks转换为{hookEvent: [cb1, cb2 ...], ...}形式 cbs[hooks[i]].push(modules[j][hooks[i]]) } } } // .... return function patch (oldVnode, vnode, hydrating, removeOnly) { // ... }}2.2、模板编译 模板编译就是解析指令参数,具体解构后的ASTElement如下所示: { tag: 'input', parent: ASTElement, directives: [ { arg: null, // 参数 end: 56, // 指令结束字符位置 isDynamicArg: false, // 动态参数,v-xxx[dynamicParams]='xxx'形式调用 modifiers: undefined, // 指令修饰符 name: "model", rawName: "v-model", // 指令名称 start: 36, // 指令开始字符位置 value: "inputValue" // 模板 }, { arg: null, end: 67, isDynamicArg: false, modifiers: undefined, name: "focus", rawName: "v-focus", start: 57, value: "" } ], // ...}2.3、生成渲染方法 vue推荐采用指令的方式去操作DOM,由于自定义指令可能会修改DOM或者属性,所以避免指令对模板解析的影响,在生成渲染方法时,首先处理的是指令,如v-model,本质是一个语法糖,在拼接渲染函数时,会给元素加上value属性与input事件(以input为例,这个也可以用户自定义)。 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) // 处理v-model指令时添加的属性 }, on: { "input": function($event) { // 处理v-model指令时添加的自定义事件 if ($event.target.composing) return; inputValue = $event.target.value } } })])}2.4、生成VNode vue的指令设计是方便我们操作DOM,在生成VNode时,指令并没有做额外处理。 2.5、生成真实DOM 在vue初始化过程中,我们需要记住两点: 状态的初始化是 父 ->子,如beforeCreate、created、beforeMount,调用顺序是 父 -> 子
真实DOM挂载顺序是 子 -> 父,如mounted,这是因为在生成真实DOM过程中,如果遇到组件,会走组件创建的过程,真实DOM的生成是从子到父一级级拼接。
在patch过程中,每此调用createElm生成真实DOM时,都会检测当前VNode是否存在data属性,存在,则会调用invokeCreateHooks,走初创建的钩子函数,核心代码如下:
// src/core/vdom/patch.jsfunction createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { // ... // createComponent有返回值,是创建组件的方法,没有返回值,则继续走下面的方法 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } const data = vnode.data // .... if (isDef(data)) { // 真实节点创建之后,更新节点属性,包括指令 // 指令首次会调用bind方法,然后会初始化指令后续hooks方法 invokeCreateHooks(vnode, insertedVnodeQueue) } // 从底向上,依次插入 insert(parentElm, vnode.elm, refElm) // ... }
以上是指令钩子方法的第一个入口,是时候揭露directive.js神秘的面纱了,核心代码如下:
// src/core/vdom/modules/directives.js// 默认抛出的都是updateDirectives方法export default { create: updateDirectives, update: updateDirectives, destroy: function unbindDirectives (vnode: VNodeWithData) { // 销毁时,vnode === emptyNode 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) // 插入后的回调 const dirsWithInsert = [ // 更新完成后回调 const dirsWithPostpatch = [] let key, oldDir, dir for (key in newDirs) { oldDir = oldDirs[key] dir = newDirs[key] // 新元素指令,会执行一次inserted钩子方法 if (!oldDir) { // new directive, bind callHook(dir, 'bind', vnode, oldVnode) if (dir.def && dir.def.inserted) { dirsWithInsert.push(dir) } } else { // existing directive, update // 已经存在元素,会执行一次componentUpdated钩子方法 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) { // 真实DOM插入到页面中,会调用此回调方法 const callInsert = () => { for (let i = 0; i
< dirsWithInsert.length; i++) { callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode) } } // VNode合并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) } } }} 对于首次创建,执行过程如下: 1.oldVnode === emptyNode,isCreate为true,调用当前元素中所有bind钩子方法。 2.检测指令中是否存在inserted钩子,如果存在,则将insert钩子合并到VNode.data.hooks属性中。 3.DOM挂载结束后,会执行invokeInsertHook,所有已挂载节点,如果VNode.data.hooks中存在insert钩子。则会调用,此时会触发指令绑定的inserted方法。 一般首次创建只会走bind和inserted方法,而update和componentUpdated则与bind和inserted对应。在组件依赖状态发生改变时,会用VNode diff算法,对节点进行打补丁式更新,其调用流程: 1.响应式数据发生改变,调用dep.notify,通知数据更新。 2.调用patchVNode,对新旧VNode进行差异化更新,并全量更新当前VNode属性(包括指令,就会进入updateDirectives方法)。 3.如果指令存在update钩子方法,调用update钩子方法,并初始化componentUpdated回调,将postpatch hooks挂载到VNode.data.hooks中。 4.当前节点及子节点更新完毕后,会触发postpatch hooks,即指令的componentUpdated方法 核心代码如下: // src/core/vdom/patch.jsfunction patchVnode ( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) { // ... const oldCh = oldVnode.children const ch = vnode.children // 全量更新节点的属性 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)) { // 调用postpatch钩子 if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } } unbind方法是在节点销毁时,调用invokeDestroyHook,这里不做过多描述。 三、注意事项 使用自定义指令时,和普通模板数据绑定,v-model还是存在一定的差别,如虽然我传递参数(v-xxx='param')是一个引用类型,数据变化时,并不能触发指令的bind或者inserted,这是因为在指令的声明周期内,bind和inserted只是在初始化时调用一次,后面只会走update和componentUpdated。 指令的声明周期执行顺序为bind ->inserted -> update -> componentUpdated. If the instruction needs to depend on the content of the subcomponent, it is recommended to write the corresponding business logic in componentUpdated.
In vue, many methods are loop calls, such as hooks methods, event callbacks, etc., and general calls are wrapped in try catch. The purpose of this is to prevent a processing method from reporting errors and causing the entire program to crash. This point can be used for reference in our development process.
"Vue instruction implementation principle is what" content introduced here, thank you for reading. If you want to know more about industry-related knowledge, you can pay attention to the website. Xiaobian will output more high-quality practical articles for everyone!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 275
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.