# 实现原理
# 简易 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
}
}
};