1、渲染器的实现
功能一:h 函数,用于返回一个VNode 对象
功能二:mount 函数,用于将VNode 挂载到DOM上;
功能三:patch 函数,用于将两个VNode 进行对比,决定如何处理新的VNode;
const h = (tag, props, children) => {
// vnode -> javascript 对象
return {
tag,
props,
children
}
}
const mount = (vnode, container) => {
// vnode -> element
// 1. 创建出真实的元素, 并且在vnode 上保留el
const el = vnode.el = document.createElement(vnode.tag);
// 2. 处理props
if(vnode.props) {
for(const key in vnode.props) {
const value = vnode.props[key];
// 对事件监听的判断
if(key.startsWith("on")){
el.addEventListener(key.slice(2).toLowerCase(), value)
} else {
el.setAttribute(key, value);
}
}
}
// 3. 处理children
if(vnode.children) {
if(typeof vnode.children === "string") {
el.textContent = vnode.children
} else {
vnode.children.forEach(item => {
mount(item, el);
})
}
}
// 4. 将el 挂载到container 上
container.appendChild(el);
}
const patch = (n1, n2) => {
if(n1.tag !== n2.tag) {
const n1ElParent = n1.el.parentElement;
n1ElParent.removeChild(n1.el);
mount(n2, n1ElParent);
} else {
// 1. 取出element 对象,并且在n2 中进行保存
const el = n2.el = n1.el;
// 2. 处理props
const oldProps = n1.props || {};
const newProps = n2.props || {};
// 2.1 获取所有的newProps 添加到el
for(const key in newProps) {
const oldValue = oldProps[key];
const newValue = newProps[key];
if(newValue !== oldValue) {
// 对事件监听的判断
if(key.startsWith("on")){
el.addEventListener(key.slice(2).toLowerCase(), newValue)
} else {
el.setAttribute(key, newValue);
}
}
}
// 2.2 删除旧的props
for(const key in oldProps) {
if(key.startsWith("on")){
const value = oldProps[key];
el.removeEventListener(key.slice(2).toLowerCase(), value)
}
if(!(key in newProps)) {
el.removeAttribute(key);
}
}
// 3. 处理children
const oldChildren = n1.children || [];
const newChildren = n2.children || [];
if(typeof newChildren === "string") {
// 情况一: newChildren 本身是一个string
// 边界情况 (edge case)
if(typeof oldChildren === "string") {
if(newChildren !== oldChildren) {
el.textContent = newChildren
}
} else {
el.innerHTML = newChildren;
}
} else {
// 情况二: newChildren本身是一个数组
if(typeof oldChildren === "string") {
el.innerHTML = "";
newChildren.forEach(item => {
mount(item, el);
})
} else {
// oldChildren: [v1,v2,v3,v8,v9]
// newChildren: [v1,v5,v6]
// 1.前面有相同节点的原生进行patch 操作
const commonLength = Math.min(oldChildren.length, newChildren.length);
for(let i = 0; i < commonLength; i++) {
patch(oldChildren[i], newChildren[i]);
}
// 2.newChildren.length > oldChildren.length
if(newChildren.length > oldChildren.length) {
newChildren.slice(oldChildren.length).forEach(item => {
mount(item, el);
})
}
// 3.newChildren.length < oldChildren.length
if(newChildren.length < oldChildren.length) {
oldChildren.slice(newChildren.length).forEach(item => {
el.removeChild(item.el);
})
}
}
}
}
}
2、响应式系统的实现
// reactive.js
class Dep {
constructor() {
this.subscriber = new Set();
}
depend() {
if(activeEffect) {
this.subscriber.add(activeEffect);
}
}
notify() {
this.subscriber.forEach(effect => {
effect();
})
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// Map({key: value}): key是一个字符串
// weakMap({key(对象): value}): key是一个对象,弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
// 1. 根据对象(target) 取出对应的Map 对象
let depsMap = targetMap.get(target);
if(!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 2.取出具体的dep 对象
let dep = depsMap.get(key);
if(!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
// 1. vue2 对raw进行数据劫持
// function reactive(raw) {
// Object.keys(raw).forEach(key => {
// const dep = getDep(raw, key);
// let value = raw[key];
// Object.defineProperty(raw, key, {
// get() {
// dep.depend();
// return value;
// },
// set(newValue) {
// if(value !== newValue) {
// value = newValue;
// dep.notify();
// }
// }
// })
// })
// return raw;
// }
// 2. vue3 对raw进行数据劫持
function reactive(raw) {
return new Proxy(raw, {
get(target, key) {
const dep = getDep(target, key);
dep.depend()
return target[key];
},
set(target, key, newValue) {
const dep = getDep(target, key);
target[key] = newValue;
dep.notify()
}
})
}
// 测试代码
const info = reactive({counter: 100, name: "why"});
const foo = reactive({height: 1.88});
// watchEffect1
watchEffect(function() {
console.log("effect1",info.counter * 2, info.name);
})
// watchEffect2
watchEffect(function() {
console.log("effect2",info.counter * info.counter);
})
// watchEffect3
watchEffect(function() {
console.log("effect3",info.counter + 10, info.name);
})
// watchEffect4
watchEffect(function() {
console.log("effect4",foo.height);
})
// info.counter++;
// info.name = "why"
foo.height = 2;
3、Mini-Vue 的实现
// index.js
function createApp(routComponent) {
return {
mount(selector) {
const container = document.querySelector(selector);
let isMounted = false;
let oldVNode = null;
watchEffect(function() {
if(!isMounted) {
oldVNode = routComponent.render();
mount(oldVNode, container);
isMounted = true;
} else {
const newVNode = routComponent.render();
patch(oldVNode, newVNode);
oldVNode = newVNode;
}
})
}
}
}
//index.html
<div id="app"></div>
<script src="./render.js"></script>
<script src="./reactive.js"></script>
<script src="./index.js"></script>
<script>
// 1. 创建根组件
const App = {
data: reactive({
counter: 0
}),
render() {
return h("div", null, [
h("h2", null, `当前计数: ${this.data.counter}`),
h("button", { onClick:() => {
this.data.counter++
} }, "+1")
])
}
}
// 2. 挂载根组件
const app = createApp(App);
app.mount("#app");
</script>