微型Vue框架构建Part4——Render渲染前

297 阅读3分钟

概述

  • 在上文 $Mount方法实现中,我们根据入口点分析了分析了HTML结构,且为每一个HTML节点构建了虚拟DOM(vnode)。本文将在此基础上实现Render渲染前的一些准备工作,为Render渲染数据打下基础。
  • 后面渲染数据时,我们就能根据模板语法和vnode的关系,找到对应的数据变量,通过虚拟DOM渲染到页面中
  • 目标概览 image.png

分析页面中哪些地方用了模板语法

  • HTML代码
    <div id="app">
        文本内容:
        <span class="a">{{content}}</span>
        <span class="b">{{info.school}}</span>
    </div>
  • 根据上文,为每一个节点构建的虚拟DOM(vnode),如下图
  • 我们能看到打印出的vnode节点中,text属性值就是页面中用到模板语法 image.png

入口方法——parpaerRender

    /**
     * mount方法的具体实现
     * @param {*} vm
     * @param {*} el 挂载节点对应的真实DOM
     */
    function mount(vm, el) {
      // 第一步:构建虚拟DOM
      const rootDom = getDom(el);
      vm._vnode = contrucVNode(vm, rootDom, null);

      // 第二步:收集DOM节点和模板语法的映射关系——待实现
      parpaerRender(vm, vm._vnode);
    }

解析vnode和模板愈发的关系

  • 我们不仅需要知道页面中哪些地方用到了模板语法,还需要知道他们之间映射关系。后期我们修改数据时能够同步页面中的展示内容。
  • 什么样的映射关系?:
    1. 单个的vnode节点中,用到了哪些模板语法
    2. 单个的模板语法,有哪些vnode节点用到了。
  • 使用ES6中 Map对象

代码实现——parpaerRender方法

  • nodeType为3,表示是以一个文本节点
  • analysisTemplteString:分析模板
    /**
    * 分析虚拟DOM中的模板语法和文本
    * @param {*} vm
    * @param {*} vnode
    */
   export function parpaerRender(vm, vnode) {
     if (vnode.nodeType === 3) {
       analysisTemplteString(vnode);
     }

     for (let i = 0; i < vnode.children.length; i++) {
       parpaerRender(vm, vnode.children[i]);
     }
   }

代码实现——analysisTemplteString()

  • 用正则表达式找到单个vnode中有哪些模板语法,形如{{xxx}}
  • 有使用到模板语法的,就拿去生成对应的映射关系
      /**
      * 分析文本类型的虚拟DOM
      * @param {*} vnode
      */
     function analysisTemplteString(vnode) {
       console.log("-vnode--", vnode);
       const template = vnode.text.match(/{{[\s|a-zA-Z0-9_.|\s]+}}/g);
       if (template && template.length) {
         for (let i = 0; i < template.length; i++) {
           // 收集依赖
           setTemplateToVNode(template[i], vnode);
           setVNodeToTemplate(template[i], vnode);
         }
       }
     }
    

得到映射关系

  • 下图是我们得到的映射关系,templateToVNode打印结果,可以看出使用到contnet变量的,只用一个vnode节点;VNodeToTemplat打印的结果,可以看出单个vnode节点下,使用到的模板语法

image.png

  • 代码实现
    const templateToVNode = new Map();
    const VNodeToTemplate = new Map();
    
    /**
     *
     * @param {*} template
     * @returns 模板语法内容,形如{{xxx}},返回xxx
     */
    function getTemplate(template) {
      if (typeof template !== "string") return;
      if (template.startsWith("{{") && template.endsWith("}}")) {
        return template.substring(2, template.length - 2);
      }
      return "";
    }
    
    /**
     * 收集依赖:形如{{xxx}}的语法模板,那些vnode使用到了
     * @param {*} template
     * @param {*} vnode
     */
    function setTemplateToVNode(template, vnode) {
      const templateName = getTemplate(template);
      const setVNode = templateToVNode.get(templateName);
      if (setVNode) {
        setVNode.push(vnode);
      } else {
        templateToVNode.set(templateName, [vnode]);
      }
    }

    /**
     *  收集依赖:单一的vnode节点, 用到了哪些形如{{xxx}}的语法模板
     * @param {*} template
     * @param {*} vnode
     */
    function setVNodeToTemplate(template, vnode) {
      const setTemplate = VNodeToTemplate.get(vnode);
      if (setTemplate) {
        setTemplate.push(template);
      } else {
        VNodeToTemplate.set(vnode, [template]);
      }
    }