仿写Vue(四):虚拟节点

250 阅读2分钟

四、虚拟节点

自己动手实践,就会更加深刻的理解

在前面,已经解决了属性的深层次渲染,接下来需要解决虚拟节点的问题——即将DOM转换为vnode。


01、虚拟节点


简单起见,简单起见,对于一个虚拟节点,有这么些属性:

  1. tag:表示节点的标签名
  2. data:表示节点的属性
  3. text:表示文本节点的文本(不为文本节点时,为undefined)
  4. type:节点的类型
  5. children:子节点
/**
 * 虚拟节点
 */
class MyVNode {
  /**
   * 构造函数
   * @param {string} tag 标签名
   * @param {object} data 标签拥有的属性
   * @param {string} text 文本节点中的文本(非文本节点则为undefined)
   * @param {number} type 1代表元素节点,3代表文本节点
   */
  constructor(tag, data, text, type) {
    this.tag = tag && tag.toLowerCase();
    this.data = data;
    this.text = text;
    this.type = type;
    this.children = [];
  }
  /**
   * 添加子节点
   */
  appendChild(vnode) {
    this.children.push(vnode);
  }
}


02、将dom转换为vnode


这里只考虑元素节点(type=1)和文本节点(type=3),其他类型的节点还有:属性节点、注释节点、文档节点等,总共12中节点。
首先拿到一个节点的 nodeType, nodeName, nodeValue, attributes, childNodes 属性,然后根据 nodeType 的不同来生成相应的vnode,以及是否需要递归调用来添加子节点。

/**
 * 将真实节点转换为虚拟节点
 */
function getVNode(node) {
  const {nodeType, nodeName, nodeValue, attributes, childNodes} = node;
  let _vnode = null;
  switch (nodeType) {
    case 1:
      // 元素节点
      const _attrObj = {};
      for (const {nodeName, nodeValue} of attributes) {
        _attrObj[nodeName] = nodeValue;
      }
      _vnode = new MyVNode(nodeName, _attrObj, undefined, nodeType);
      for (const node of childNodes) {
        _vnode.appendChild(getVNode(node));
      }
      break;
    case 3:
      // 文本节点
      _vnode = new MyVNode(undefined, undefined, nodeValue, nodeType)
      break;
  }
  return _vnode;
}


03、vnode转换为dom节点


同样根据type的值来生成相应的节点。

/**
 * 将虚拟节点转换为真正DOM节点
 * @param {MyVNode} vnode 虚拟节点
 */
function parseVNode(vnode) {
  const { tag, type, data, text, children} = vnode;
  let _node;
  switch(type) {
    case 1: // 元素节点
      _node = document.createElement(tag);
      // 属性
      Object.entries(data).forEach(([key, value]) => {
        _node.setAttribute(key, value);
      })
      // 递归生成子元素
      for (const child of children) {
        _node.appendChild(parseVNode(child));
      }
      break;
    case 3:
      _node = document.createTextNode(text);
      break;
  }
  return _node;
}

04、效果图


html部分:

  <article id="root">
    <section title="name">romeo
      <p>p1</p>
      <p>p2</p>
    </section>
    <section title="message" id="message">wants to be rich</section>
  </article>

js其余部分:

const root = document.querySelector('#root');
const vroot = getVNode(root);
const newRoot = parseVNode(vroot);
document.body.appendChild(newRoot)

可以看到内容成功的复制了一份:

image.gif

源代码在github上,请点击阅读原文查看~
感兴趣的小伙伴请关注我的公众号~
公众号二维码.png