手写系列 | 如何将虚拟DOM转换成真实DOM

196 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

关于虚拟DOM的原理、与真实DOM的关系、以及源码的实现思路和分析等,可参考以之前的几篇文章:

Vue进阶 | 虚拟DOM (一)

Vue进阶 | 虚拟DOM (二)源码浅析

Vue进阶 | 虚拟DOM(三)Diff算法

真实 DOM 和 虚拟 DOM 的区别,在于是否直接操作 DOM ,如下图:

真实DOM: image.png

使用虚拟DOM:

image.png

虚拟 DOM 树由无数个虚拟节点组成。在实现的过程中,我们会用数据(js对象)来存储虚拟 DOM ,这个 js 对象包含以下三个属性:

  • tag : 标签,比如 div
  • props/ attrs :属性,比如 id、class 等
  • children :子节点

我们平时熟悉的 HTML:

image.png

转换成虚拟的之后,就是 JS 对象所表示的 DOM 树:

image.png

代码实现

实现思路:

  1. 定义一个表示虚拟 DOM 的 js 对象,作为转化的数据
  2. 判断类型。如果是数字类型,转化为字符串,再生成文本节点;如果是字符串类型直接就是文本节点(我们所需要的是字符串类型)
  3. document.createElement() 用于创建 DOM 节点
  4. 如果存在属性,则遍历所有的属性,在刚才生成的 DOM 上添加属性及其值(dom.setAttribute(key, value))
  5. 遍历当前 DOM 节点的子节点,添加子节点(dom.appendChild()),参数通过递归传入(第2条中提到的字符串类型,也是递归的终止条件)
  6. 最终可以获得一个有父子结构的 DOM,输出或渲染即可
const vnode = {
      tag: 'div',
      attrs: {
          id: 'app'
      },
      children: [{
          tag: 'h1',
          attrs: {
              className: 'title'
          },
          children: [
              '俺是一个标题'
          ]
      },{
          tag: 'ul',
          children: [{
              tag: 'li',
              children: ['苹果','香蕉']
          },{
              tag: 'li',
              children: ['土豆','白菜']
          }]
      }]
  }

const render = (vnode) => {
  
  if(typeof vnode === 'number') {
    vnode = String(vnode)
  }
  if (typeof vnode === 'string') {
    return document.createTextNode(vnode);
  }
  // DOM
  const dom = document.createElement(vnode.tag);
  // 遍历属性
  if (vnode.attrs) {
    Object.keys(vnode.attrs).forEach( childNode => {
      dom.setAttribute(childNode, vnode.attrs[childNode])
    })
  }
  // 递归子节点
  if(vnode.children) {
    console.log(vnode.children);
    vnode.children.forEach(child => {
      dom.appendChild(render(child));
    })
  }
  return dom;
}

const dom = render(vnode)
console.log(dom)

运行结果

image.png