• .12-Vue源码之patch(2)


    快完事咯!  

      简单看了下patch函数,虽然不长,但是实际上很长很长,慢慢来吧,

      首先来个总览:

        // line-5250
        // oldVnode => 原生DOM节点
        // vnode => 虚拟DOM
        // hydrating => undefined
        // removeOnly => false
        // 后面两个undefined
        function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
            // 判断是否存在虚拟DOM
            if (isUndef(vnode)) {
                if (isDef(oldVnode)) {
                    invokeDestroyHook(oldVnode);
                }
                return
            }
    
            var isInitialPatch = false;
            var insertedVnodeQueue = [];
    
            if (isUndef(oldVnode)) {
                // empty mount (likely as component), create new root element
                // 空挂载或作为组件
                isInitialPatch = true;
                createElm(vnode, insertedVnodeQueue, parentElm, refElm);
            } else {
                var isRealElement = isDef(oldVnode.nodeType);
                if (!isRealElement && sameVnode(oldVnode, vnode)) {
                    // patch existing root node
                    patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
                } else {
                    if (isRealElement) {
                        // 判断是否SSR
                        if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
                            oldVnode.removeAttribute(SSR_ATTR);
                            hydrating = true;
                        }
                        // hydrating这个到底是啥????
                        if (isTrue(hydrating)) {
                            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
                                invokeInsertHook(vnode, insertedVnodeQueue, true);
                                return oldVnode
                            } else {
                                warn(
                                    'The client-side rendered virtual DOM tree is not matching ' +
                                    'server-rendered content. This is likely caused by incorrect ' +
                                    'HTML markup, for example nesting block-level elements inside ' +
                                    '<p>, or missing <tbody>. Bailing hydration and performing ' +
                                    'full client-side render.'
                                );
                            }
                        }
                        // 两个都不是 新建空虚拟DOM
                        oldVnode = emptyNodeAt(oldVnode);
                    }
                    // 此处oldElm => div | parentElm$1 => body
                    var oldElm = oldVnode.elm;
                    var parentElm$1 = nodeOps.parentNode(oldElm);
                    createElm(
                        vnode,
                        insertedVnodeQueue,
                        // BUG:当节点处于leaving transition状态中不插入
                        oldElm._leaveCb ? null : parentElm$1,
                        // 获取相邻节点
                        nodeOps.nextSibling(oldElm)
                    );
    
                    if (isDef(vnode.parent)) {
                        // component root element replaced.
                        // update parent placeholder node element, recursively
                        var ancestor = vnode.parent;
                        while (ancestor) {
                            ancestor.elm = vnode.elm;
                            ancestor = ancestor.parent;
                        }
                        if (isPatchable(vnode)) {
                            for (var i = 0; i < cbs.create.length; ++i) {
                                cbs.create[i](emptyNode, vnode.parent);
                            }
                        }
                    }
    
                    if (isDef(parentElm$1)) {
                        removeVnodes(parentElm$1, [oldVnode], 0, 0);
                    } else if (isDef(oldVnode.tag)) {
                        invokeDestroyHook(oldVnode);
                    }
                }
            }
    
            invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
            return vnode.elm
        }

      函数总共有6个形参,其中有3个为undefined,内部总共主要是条件判断,按照顺序跑下来。

    一、

      由于不是SSR,也没有hydrating这个参数,所以会直接创建一个空的虚拟DOM作为oldVnode,如下:

        // line-4778
        function emptyNodeAt(elm) {
            return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
        }

      

    二、

      接下来到了createElm方法,代码如下:

        // line-4802
        // vnode => 之前的虚拟DOM
        // insertedVnodeQueue => [] => 空数组
        // parentElm => body => 父元素
        // refElm => 空白文本 => 节点的相邻元素
        // nested => undefined
        function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) {
            vnode.isRootInsert = !nested; // for transition enter check
            if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
                return
            }
    
            var data = vnode.data;
            var children = vnode.children;
            var tag = vnode.tag;
            if (isDef(tag)) {
                // error检测
                {
                    if (data && data.pre) {
                        inPre++;
                    }
                    // 这个错误经常见!
                    if (!inPre &&
                        !vnode.ns &&
                        !(config.ignoredElements.length && config.ignoredElements.indexOf(tag) > -1) &&
                        config.isUnknownElement(tag)
                    ) {
                        warn(
                            'Unknown custom element: <' + tag + '> - did you ' +
                            'register the component correctly? For recursive components, ' +
                            'make sure to provide the "name" option.',
                            vnode.context
                        );
                    }
                }
                // ns???
                vnode.elm = vnode.ns ?
                    nodeOps.createElementNS(vnode.ns, tag) :
                    nodeOps.createElement(tag, vnode);
                setScope(vnode);
    
                /* istanbul ignore if */
                {
                    createChildren(vnode, children, insertedVnodeQueue);
                    if (isDef(data)) {
                        invokeCreateHooks(vnode, insertedVnodeQueue);
                    }
                    insert(parentElm, vnode.elm, refElm);
                }
    
                if ("development" !== 'production' && data && data.pre) {
                    inPre--;
                }
            }
            // 注释 
            else if (isTrue(vnode.isComment)) {
                vnode.elm = nodeOps.createComment(vnode.text);
                insert(parentElm, vnode.elm, refElm);
            }
            // 处理纯文本
            else {
                vnode.elm = nodeOps.createTextNode(vnode.text);
                insert(parentElm, vnode.elm, refElm);
            }
        }

      函数进入就会调用createComponent进行判断,这个函数暂时看不太懂,一个分支都没有进,直接返回了undefined:

        // line-4855
        function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
            // 获取属性
            var i = vnode.data;
            if (isDef(i)) {
                var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
                if (isDef(i = i.hook) && isDef(i = i.init)) {
                    i(vnode, false /* hydrating */ , parentElm, refElm);
                }
                // after calling the init hook, if the vnode is a child component
                // it should've created a child instance and mounted it. the child
                // component also has set the placeholder vnode's elm.
                // in that case we can just return the element and be done.
                if (isDef(vnode.componentInstance)) {
                    initComponent(vnode, insertedVnodeQueue);
                    if (isTrue(isReactivated)) {
                        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
                    }
                    return true
                }
            }
        }

      下一步是跳到createElement函数,nodeOps中的方法都是自封装的DOM操作方法,超级简单。

        // line-4599
        function createElement$1(tagName, vnode) {
            var elm = document.createElement(tagName);
            if (tagName !== 'select') {
                return elm
            }
            // 对select做特殊处理
            // false or null will remove the attribute but undefined will not
            if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
                elm.setAttribute('multiple', 'multiple');
            }
            return elm
        }

      可以看,这个方法也就是创建一个DOM节点并返回,对select会做特殊处理,于是vnode.elm也就是个普通的DOM节点。

      接下来是setScope函数(),该函数目的是给根节点添加一个特殊的ID,避免css样式影响其他模块。

      这个东西我就熟悉啦,触发形式是这样:

      渲染结果是这样:

      这样可以保证所有的样式都是独立的,就算名字一样,后面添加的随机字符串可以保证唯一。

        // line-4958
        function setScope(vnode) {
            var i;
            var ancestor = vnode;
            while (ancestor) {
                if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
                    nodeOps.setAttribute(vnode.elm, i, '');
                }
                ancestor = ancestor.parent;
            }
            // for slot content they should also get the scopeId from the host instance.
            if (isDef(i = activeInstance) &&
                i !== vnode.context &&
                isDef(i = i.$options._scopeId)) {
                nodeOps.setAttribute(vnode.elm, i, '');
            }
        }

      函数判断虚拟DOM是否存在_scopeId,然后对每一个节点添加对应的Id,这里没有,所以就会跳过去。

      

      接下来调用createChildren方法,从名字也能看出来是对子节点的处理。实际上,也是对子节点或者纯文本进行递归处理:

        // line-4927
        function createChildren(vnode, children, insertedVnodeQueue) {
            // 存在复杂子节点
            if (Array.isArray(children)) {
                for (var i = 0; i < children.length; ++i) {
                    createElm(children[i], insertedVnodeQueue, vnode.elm, null, true);
                }
            }
            // 子节点为纯文本
            else if (isPrimitive(vnode.text)) {
                nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text));
            }
        }

      第一条分支处理DOM子节点,直接递归。本例子节点只是一个纯文本:

      因此,重新跳入createElm方法后,会直接进入第三个分支,即:

        // line-4802
        function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) {
            // 属性获取
            // ...
            if (isDef(tag)) {
                // 处理节点类型
            } else if (isTrue(vnode.isComment)) {
                // 处理注释
            } else {
                vnode.elm = nodeOps.createTextNode(vnode.text);
                insert(parentElm, vnode.elm, refElm);
            }
        }

      这个分支只有两条语句,第一条会创建一个文本节点给elm属性,第二条负责插入。

      第一个DOM操作只是简单调用document.createTextNode方法,直接看第二个函数吧:

        // line-4915
        // parent => div => 父节点
        // elm => #text => 本节点
        // ref => null => 相邻节点
        function insert(parent, elm, ref) {
            if (isDef(parent)) {
                if (isDef(ref)) {
                    if (ref.parentNode === parent) {
                        // 
                        nodeOps.insertBefore(parent, elm, ref);
                    }
                } 
                // parent.appendChild(elm)
                else {
                    nodeOps.appendChild(parent, elm);
                }
            }
        }

      第二个也是DOM操作!好吧,比较简单,看看就懂了。

      

      子节点处理完后,接下来处理本节点的属性,相关函数为invokeCreateHooks:

        // line-4944
        // vnode => 虚拟DOM
        // insertedVnodeQueue => []
        function invokeCreateHooks(vnode, insertedVnodeQueue) {
            // cbs为工具对象
            for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
                cbs.create[i$1](emptyNode, vnode);
            }
            i = vnode.data.hook; // Reuse variable
            if (isDef(i)) {
                if (isDef(i.create)) {
                    i.create(emptyNode, vnode);
                }
                if (isDef(i.insert)) {
                    insertedVnodeQueue.push(vnode);
                }
            }
        }

      比较重要的是这里的cbs对象,在外部函数运行最开始有进行初始化,如下:

        // line-4762
        function createPatchFunction(backend) {
            var i, j;
            var cbs = {};
            // line-6968
            // var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
            var modules = backend.modules;
            var nodeOps = backend.nodeOps;
    
            for (i = 0; i < hooks.length; ++i) {
                cbs[hooks[i]] = [];
                for (j = 0; j < modules.length; ++j) {
                    if (isDef(modules[j][hooks[i]])) {
                        cbs[hooks[i]].push(modules[j][hooks[i]]);
                    }
                }
            }
    
            // tools fn
    
            // return fn
        }

      这个对象来源于多个对象的拼接,包含大量虚拟DOM的操作方法,内容如图:

      从名字可见,该对象整合了创建、摧毁、移除、更新等功能库,此处仅用create来创建,内部函数如图:

      invokeCreateHooks函数一开始遍会遍历此数组,依次传参执行,参数为空vnode与当前的vnode。

      这部分比较长,下次再讲。

      

  • 相关阅读:
    创建型设计模式-原型模式(单例) MemberwiseClone()
    Oracle 查看没有释放的链接 和删除,相关sql
    win10 安装 SQL Developer 工具
    修改nuget包默认存放路径 win10
    使用端口查询
    未能加载文件或程序集“Newtonsoft.Json, Version=12.0.0.0,
    微信错误码
    sqlserver 时间转换记录
    Homebrew 使用指南
    在Mac检查安装的.net core 版本
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/7207242.html
Copyright © 2020-2023  润新知