Skip to content

实现一个简易版的虚拟 DOM

步骤

  1. 拿到挂载节点
typescript
new Vue({
  el: "#app",
});
  1. 获取节点列表
typescript
let data: Vnode = {
  tag: "div",
  children: [
    {
      tag: "section",
      text: "子节点1",
    },
    {
      tag: "section",
      text: "子节点2",
    },
    {
      tag: "ul",
      text: "子节点3",
      children: [
        {
          tag: "li",
          text: "子子节点1",
        },
      ],
    },
  ],
};
  1. 渲染 DOM
typescript
let app = document.querySelector(el);
let dom = render(data);
app.appendChild(dom);

render 函数

typescript
render(data: Vnode): Node {
  // 创建元素
  let root = this.createElement(data.tag);
  // 有文本填充文本
  if (data.text) {
    this.setText(root, data.text);
  }
  // 如果存在children并且是数组则进入递归
  if (data.children && Array.isArray(data.children)) {
    data.children.forEach((item) => {
      let child = this.render(item);
      root.appendChild(child);
    });
    // 否则直接在节点内填充文本
  } else {
    this.setText(root, data.text);
  }
  // 返回创建好的DOM
  return root;
}

全部代码

typescript
interface Options {
  /**
   * @param el 实例挂载的页面节点
   */
  el: string | HTMLElement;
}

interface VueCls {
  /**
   * @param options 实例化Vue的参数
   */
  options: Options;
  /**
   * @function init 创建虚拟DOM节点, 渲染真实DOM
   */
  init(): void;
}

interface Vnode {
  /**
   * @param tag 标签: div,span,p等
   */
  tag: string;
  /**
   * @param text 标签里的文字
   */
  text?: string;
  /**
   * @param children 子节点, 参与render()递归
   */
  children?: Vnode[];
}

//  简单版虚拟DOM实现
class Dom {
  // 创建节点
  createElement(el: string) {
    return document.createElement(el);
  }
  // 填充文本
  setText(el: HTMLElement, text: string | null) {
    el.textContent = text;
  }
  // 渲染函数
  render(data: Vnode): Node {
    let root = this.createElement(data.tag);
    if (data.text) {
      this.setText(root, data.text);
    }
    // 如果存在children并且是数组则进入递归
    if (data.children && Array.isArray(data.children)) {
      data.children.forEach((item) => {
        let child = this.render(item);
        root.appendChild(child);
      });
      // 否则直接在节点内填充文本
    } else {
      this.setText(root, data.text);
    }
    return root;
  }
}

//   继承Dom
class Vue extends Dom implements VueCls {
  options: Options;
  constructor(options: Options) {
    // 父类的prototype.constructor.call
    super();
    this.options = options;
    this.init();
  }
  init(): void {
    // 虚拟Dom: 通过js渲染真实Dom
    let data: Vnode = {
      tag: "div",
      children: [
        {
          tag: "section",
          text: "子节点1",
        },
        {
          tag: "section",
          text: "子节点2",
        },
        {
          tag: "ul",
          text: "子节点3",
          children: [
            {
              tag: "li",
              text: "子子节点1",
            },
          ],
        },
      ],
    };
    // 获取挂载节点
    let app =
      typeof this.options.el == "string"
        ? document.querySelector(this.options.el)
        : this.options.el;
    app.appendChild(this.render(data));
  }
}

new Vue({
  el: "#app",
});