Vue中directive原理分析二

1. Vue怎么获取到设置的指令钩子

以下面一段Vue模板为例

<div v-test v-test2></div>

以上的模板会被编译成渲染函数

with(this) {
    return _c('div', {
        directives: [{
            name: 'test',
            rawName: 'v-test'
        }, {
            name: 'test2',
            rawName: 'v-test2'
        }]
    })
}

如何获取到我们设置的指令的钩子,入口函数为updateDirectives, 用来获取所有的新指令和旧指令集合。

function updateDirectives(oldVnode, vnode) { 
    // 获取旧节点的指令  
    var oldDirs = normalizeDirectives$1(
            oldVnode.data.directives, 
            oldVnode.context);   
    // 获取新节点的指令
    var newDirs = normalizeDirectives$1(
            vnode.data.directives, 
            vnode.context);  
}

可以看到以上主要使用到了normalizeDirectives$1函数,这个函数就是获取指令的核心方法

function normalizeDirectives$1(dirs, name) {
    var res = {};
    var i, dir;
    // 遍历本节点的所有的指令,逐个从组建中获取
    for (i = 0; i < dirs.length; i++) {
        dir = dirs[i];
        res[dir.name] = dir;
        // 将获取到的钩子添加到当前指令上
        dir.ref = vm.$options['directives'][dir.name];
    }
    return res;
}

经过以上这一步的处理,我们的directives会变成一下这个样子

directives: [{
    name: 'test',
    rawName: 'v-test',
    ref: {
        bind() {},
        unbind() {}
    }
}]

2. 内部如何调用钩子函数

上面一篇文章已经说过,bind, update, unbind都是直接触发的。

  • bind 当指令在新节点中存在,但是旧节点中不存在时,就会调用bind
  • update 当指令在新旧节点中都存在时,会调用update
  • unbind 当指令在新节点中不存在,旧节点中存在时,会调用unbind

下面主要来看insertedcomponentUpdated

inserted是在DOM插入父节点之后才触发,而处理inserted是在DOM插入之前,所以这里不可能直接触发,inserted分为保存和执行两个步骤

function updateDirectives(oldVnode, vnode) { 
    // 如果旧节点为空,表示这是新创建的
    var isCreate = oldVnode === emptyNode;  
    var dirsWithInsert = [];     
    var key, oldDir, dir; 
    for (key in newDirs) {
        oldDir = oldDirs[key];
        dir = newDirs[key];  
        if (!oldDir) {             
            if (dir.def && dir.def.inserted) {
                // 将所有的inserted钩子保存到dirsWithInsert数组
                dirsWithInsert.push(dir);
            }
        } 
    }   

    if (dirsWithInsert.length) {        
        // 用所有的inserted钩子组装成函数 callInserted
        var callInsert = function() {            
            for (var i = 0; i < dirsWithInsert.length; i++) {
                callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
            }
        };        
        // 合并到hook中的insert
        if (isCreate) {
            // 把callInsert 和本节点的 insert 合并起来
            vnode.data.hook['insert'] = callInsert
        } else {
            callInsert();
        }
    }   
}

当页面初始化时,调用patch处理根节点,开始插入页面的步骤,其中会不断遍历子节点

function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {  
    var insertedVnodeQueue=[]   
    if(需要更新){...省略...}
    // 不是更新,而是页面初始化
    else{        
    // 其中会不断地遍历子节点,递归等....
        createElm(vnode,insertedVnodeQueue,...);
        invokeInsertHook(vnode, insertedVnodeQueue);
    }    
    return vnode.elm
}

上面的createElement会创建本节点以及其后代节点,然后插入到父节点中,等到createElm执行完,所有的节点都已经插入完毕了

function createElm(    
    vnode,insertedVnodeQueue,
    parentElm,refElm
){    
    vnode.elm = document.createElement(vnode.tag);    
    // 不断遍历子节点,递归调用 createElm
    if (Array.isArray(children)) {        
        for (var i = 0; i < children.length; ++i) {
            createElm(children[i], insertedVnodeQueue,
                vnode.elm, null, true, children, i);
        }
    }
    // 处理本节点的事件,属性等,其中包含对指令的处理
    invokeCreateHooks(vnode, insertedVnodeQueue);    
    // 插入 本DOM 到父节点中
    insert(parentElm, vnode.elm, refElm); 
}

整个createElm之后,会调用invokeInsertHook,这就是调用insert的时机,我们可以看看invokeInsertHook做了什么

// 将之前存储的insert钩子一次性全部触发
function invokeInsertHook(vnode, insertedVnodeQueue) {    
    for (var i = 0; i < insertedVnodeQueue.length; ++i) {
        insertedVnodeQueue[i].data.hook.insert(queue[i]);
    }
}

componentUpdated也是分为保存和执行两段源码

// 把指令componentUpdated的函数 和本节点的 postpatch 合并起来

if (dirsWithPostpatch.length) {
    vnode.data.hook['postpatch'] = function() {            
        for (var i = 0; i < dirsWithPostpatch.length; i++) {
            callHook$1(dirsWithPostpatch[i], 
                'componentUpdated', vnode, oldVnode);
        }
    });
}

componentUpdated钩子是更新一个节点就马上执行,更新一个节点的意思是包括其内部子节点的,更新一个节点也会调用patch

function patch(oldVnode, vnode) {     
    if(需要更新){  
        patchVnode(oldVnode, vnode)
    }    
    return vnode.elm  
}

function patchVnode(oldVnode, vnode){   
   // 递归调用 patchVnode 更新子节点
   updateChildren(oldVnode, vnode,.....);    
    // 执行本节点的 postpatch
   if (isDef(i = data.hook) && isDef(i = i.postpatch)) {
        i(oldVnode, vnode);        
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值