# 实现原理

# 简易 Vue 实现

# 测试用例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="./kvue-vnode.js"></script>
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
    <script>
      const app = new Vue({
        data() {
          return {
            title: "我是标题"
          };
        },
        mounted() {
          setTimeout(() => {
            this.title = "我是标题,但是我变了";
          });
        },
        render(h) {
          return h("h3", null, this.$data.title);
        }
      });
      app.$mount("#app");
      setTimeout(() => {
        app.$data.title = "我是标题,但是我变了";
      }, 2000);
    </script>
  </body>
</html>

# 完整代码

kvue-vnode.js

// 方法函数
function isString(text) {
  return typeof text === "string";
}
// 遍历观察
function observe(obj) {
  Object.keys(obj).forEach((key) => {
    defineReactive(obj, key, obj[key]);
  });
}
// 数据劫持
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      console.log("get", key);
      return val;
    },
    set(newV) {
      console.log("set", key);
      val = newV;
      app.update();
    }
  });
}

function Vue(options) {
  this.$options = options;
  this.$data = options.data();
  observe(this.$data);
  this._isMounted = false;
  this._vnode = false;
  console.log("kvue inited");
}

// (全量更新版本)
// 初始化 生成DOM
// 数据更新 更新DOM
Vue.prototype.$mount = function (selector) {
  const parent = document.querySelector(selector);
  this.update = function () {
    // 虚拟DOM
    const vnode = this.$options.render.call(this, this.createVnode);
    console.log("kvue update");
    if (!this._isMounted) {
      // 根据vnode创建dom
      this.patch(parent, vnode);
      this._isMounted = true;
      if (this.$options.mounted) {
        console.log("kvue mounted");
        this.$options.mounted.call(this);
      }
    } else {
      this.patch(this._vnode, vnode);
    }
    // 保存vnode的引用
    this._vnode = vnode;
  };
  this.update();
};

// create vnode
Vue.prototype.createVnode = function (tag, props, children) {
  return { tag, props, children };
};

// dom
Vue.prototype.createDom = function (vnode) {
  const { tag, children } = vnode;
  // 真实dom
  const el = document.createElement(tag);
  // props
  // child
  if (Array.isArray(children)) {
    // 子元素
    children.forEach((child) => {
      el.appendChild(this.createDom(child));
    });
  } else {
    // 文本
    el.textContent = children;
  }
  vnode.$el = el;
  console.log("el", el);
  return el;
};

// vnode create dom and diff vnode update dom
Vue.prototype.patch = function (n1, n2) {
  if (n1.nodeType) {
    // init
    // 创建n2对应的dom
    const child = this.createDom(n2);
    n1.appendChild(child);
    n2.$el = child;
  } else {
    // update
    const el = (n2.$el = n1.$el);
    if (n1.tag === n2.tag) {
      if (isString(n1.children)) {
        if (isString(n2.children)) {
          // text update
          if (n1.children !== n2.children) {
            el.textContent = n2.children;
          }
        } else {
          // replace text to element
        }
      } else {
        if (isString(n2.children)) {
          // replace element to text
          el.innerHTML = "";
          el.textContent = n2.children;
        } else {
          // update children
        }
      }
    } else {
      //TODO
    }
  }
};