Vue源码之指令细节

前言

指令是Vue提供的复用手段之一,除了内置的v-if、v-show、v-text等还支持自定义指令。Vue中代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。本文是通过源码来梳理指令的执行流程,从而加深对指令的理解。

指令执行流程

实际上自定义指令需要清楚相关钩子函数的时机,从而集合实际场景做相关处理,钩子函数如下:

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
  • unbind:只调用一次,指令与元素解绑时调用

结合这些钩子函数可以更好的理解指令相关逻辑。实际上指令的处理逻辑跟内置组件transition有一些相似的,这里具体聊聊相似的逻辑。实际上Vue在pacth阶段会做diff算法以达到尽可能复用节点,在这个过程中会对相关props等属性做一些处理逻辑。而Vue中对每一类属性对应是一个module对象,这里先看下所涉及到的module,如下:

var platformModules = [
	attrs,
    klass,
    events,
    domProps,
    style,
    transition
];
var baseModules = [
	ref,
    directives
];
var modules = platformModules.concat(baseModules);
// 创建patch函数,其中会有上面module的hook的处理
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });

从源码中看到涉及到module对应有:attrs、class、events、domProps、style、transition、ref、directives。实际上基本跟Vue数据对象对应的,上面一些module对象实际上都是定义的hook函数以便在patch合适时机执行(包含更新逻辑等)。例如attrs module和klass module的定义:

// attrs module
var attrs = {
	create: updateAttrs,
    update: updateAttrs
};
// klass module
var klass = {
	create: updateClass,
    update: updateClass
};

这些module相关hook都是在patch阶段用于更新相关属性值的,在transition这篇文章实际上分析了transition module的触发时机,实际上就是patch阶段invokeCreateHooks触发create hook的。directives module触发时机也是如此,下面是directives module定义:

var directives = {
	create: updateDirectives,
    update: updateDirectives,
    destroy: function unbindDirectives (vnode) {
    	updateDirectives(vnode, emptyNode);
    }
};

从directives module的定义可以很清楚的知道都是调用updateDirectives函数来处理相关逻辑的,接下面就具体看看该函数的逻辑。

updateDirectives函数
function updateDirectives (oldVnode, vnode) {
	if (oldVnode.data.directives || vnode.data.directives) {
    	_update(oldVnode, vnode);
    }
}

directives是所有指令的集合中心,只有存在directives才会执行_update方法,_update方法才是指令执行的核心,其具体逻辑如下:

在这里插入图片描述
实际上_update函数逻辑简要概括就是:指定条件下调用相关hook。核心逻辑如下:

for (key in newDirs) {
	oldDir = oldDirs[key];
    dir = newDirs[key];
    if (!oldDir) {
    	// new directive, bind
        callHook$1(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$1(dir, 'update', vnode, oldVnode);
        if (dir.def && dir.def.componentUpdated) {
        	dirsWithPostpatch.push(dir);
        }
    }
}

上面是_update函数中新指令集合的遍历处理的逻辑,其具体逻辑概括为:

比较新旧节点:

  • 对于新指令调用其bind钩子函数,并收集存在inserted钩子函数的指令
  • 对于已有指令调用其update钩子函数并更新相关,并收集存在componentUpdated钩子函数的指令

_update之后的逻辑就是处理其他Hook,具体逻辑如下:

// 处理inserted钩子函数
if (dirsWithInsert.length) {
	var callInsert = function () {
    	for (var i = 0; i < dirsWithInsert.length; i++) {
        	callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
        }
    };
    isCreate ? mergeVNodeHook(vnode, 'insert', callInsert) : callInsert();
}
// 处理componentUpdated钩子函数
if (dirsWithPostpatch.length) {
	mergeVNodeHook(vnode, 'postpatch', function () {
    	for (var i = 0; i < dirsWithPostpatch.length; i++) {
    		callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
        }
    });
 }
 // 对比旧节点中不存于新节点中的指令,调用其unbind钩子函数
 if (!isCreate) {
 	for (key in oldDirs) {
    	if (!newDirs[key]) {
        	// no longer present, unbind
            callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
        }
    }
}

实际上_update函数整体处理逻辑是非常清晰的,关键在于涉及到的相应VNode Hook和Module Hook的触发,这部分逻辑是非常复杂的,之后会专门梳理Hook相关的处理逻辑。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue.js的码非常庞大和复杂,无法在此简单概括。不过,我可以给你一个简单的概述。 Vue.js码主要分为以下几个部分: 1.编译器(Compiler):Vue.js的编译器负责将模板转为虚拟DOM渲染函数。它将模板解析为AST(抽象语法树),然后通过遍历AST生成虚拟DOM渲染函数。 2.虚拟DOM(Virtual DOM):Vue.js使用虚拟DOM来提高渲染性能。虚拟DOM是一个轻量级的JavaScript对象树,它与真实的DOM结构相对应。通过比较新旧虚拟DOM树的差异,Vue.js可以高效地更新真实的DOM。 3.响应式系统(Reactivity System):Vue.js的响应式系统使得数据的变化能够自动驱动视图的更新。当数据发生变化时,Vue.js会通过依赖追踪和观察者模式来检测和通知相关的视图更新。 4.组件系统(Component System):Vue.js的组件系统允许开发者将页面划分为独立、可重用的组件。每个组件都有自己的模板、逻辑和样式,并且可以通过props和events进行父子组件之间的通信。 5.扩展API(Extension API):Vue.js提供了一些扩展API,用于扩展和定制Vue.js的行为。开发者可以使用这些API来实现自定义指令、混入等功能。 需要注意的是,Vue.js的码非常庞大且涉及的细节非常多,理解整个码需要深入的JavaScript和前端知识。如果你对Vue.js码感兴趣,我建议你仔细阅读官方文档和参考相关的教程和资料。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值