Vue源码学习
Posted 忘忘碎斌bin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue源码学习相关的知识,希望对你有一定的参考价值。
实现一个 Mini Vue
渲染系统
上一篇:渲染系统:h函数、mount函数
patch函数
function patch(oldVNode, newVNode) {
if (oldVNode.tag !== newVNode.tag) {
const el = oldVNode.el.parentElement;
el.removeChild(oldVNode.el);
mount(newVNode, el)
} else {
const el = newVNode.el = oldVNode.el;
// 处理props
const oldProps = oldVNode.props || {};
const newProps = newVNode.props || {};
for (const key in newProps) {
const oldValue = oldProps[key];
const newValue = newProps[key];
if (newValue !== oldValue) {
if (key.startsWith("on")) {
el.addEventListener(key.split("on")[1].toLowerCase(), newValue)
} else {
el.setAttribute(key, newValue);
}
}
}
for (const key in oldProps) {
const oldValue = oldProps[key];
if (key.startsWith("on")) {
el.removeEventListener(key.split("on")[1].toLowerCase(), oldValue)
} else {
el.removeAttribute(key)
}
}
// 处理children
const oldChildren = oldVNode.children || [];
const newChildren = newVNode.children || [];
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
el.textContent = newChildren;
}
} else {
el.innerhtml = newChildren;
}
} else {
// 新的VNode是一个数组
if (typeof oldChildren === "string") {
el.innerHTML = "";
newChildren.forEach(element => {
mount(element, el)
});
} else {
const oldChildrenLength = oldChildren.length;
const newChildrenLength = newChildren.length;
const minLength = Math.min(oldChildrenLength, newChildrenLength);
for (let i = 0; i < minLength; i++) {
patch(oldChildren[i], newChildren[i])
}
if (oldChildrenLength > newChildrenLength) {
oldChildren.slice(minLength).forEach(node => {
el.removeChild(node.el)
})
}
if (newChildrenLength > oldChildrenLength) {
newChildren.slice(minLength).forEach(node => {
mount(node, el)
})
}
}
}
}
}
测试:
响应式系统
vue2的实现方式
// 依赖函数的保存
class Dep {
constructor() {
this.subscribers = []
}
depend() {
if (currentEffect) {
this.subscribers.push(currentEffect)
}
}
notify() {
this.subscribers.forEach(effect => {
effect()
})
}
}
let target = new WeakMap();
function getDep(raw, key) {
let deepMap = target.get(raw);
if (!deepMap) {
deepMap = new Map();
target.set(raw, deepMap)
}
let dep = deepMap.get(key);
if (!dep) {
dep = new Dep();
deepMap.set(key, dep)
}
return dep
}
// 通过watchEffect函数来收集依赖
let currentEffect = null;
function watchEffect(effect) {
currentEffect = effect;
effect();
currentEffect = null;
}
// 数据结构来保存数据在那些函数内有依赖
// vue内使用了 WeakMap & Map
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) {
value = newValue;
dep.notify()
}
})
})
return raw
}
// 测试
const info = reactive({ name: "coderbin", age: 21 })
watchEffect(function () {
console.log("effect1:" + info.name, info.age);
})
watchEffect(function () {
console.log("effect2:", info.age);
})
info.name = "aaa";
结果:
vue3实现响应式的方式 Proxy
Proxy的优势
- Object.defineProperty劫持对象熟悉时,新增属性,需要再次调用Object.defineProperty才能对其实现劫持,而proxy是对整个对象进行劫持
- Object.defineProperty需改原本对象,触发拦截。而proxy是修改proxy实例时触发拦截
- proxy支持 in delete 操作符的捕获器
缺点:兼容性问题。
实现:只需修改reactive函数
// vue3对raw进行数据劫持
function reactive(raw) {
let dep = null;
return new Proxy(raw, {
get(target, key) {
dep = getDep(target, key);
dep.depend();
return target[key]
},
set(target, key, newValue) {
target[key] = newValue
dep = getDep(target, key);
dep.notify()
}
})
}
入口模块(挂载函数)
参照Vue3 : createApp( App ).mount( “#app” );
function createApp(App) {
return {
mount(selectors) {
const app = document.querySelector(selectors);
let oldVnode = null;
let newVnode = null;
let isMounted = false;
watchEffect(function() {
if (!isMounted) {
oldVnode = App.render();
mount(oldVnode, app);
isMounted = true;
} else {
newVnode = App.render();
patch(oldVnode, newVnode);
oldVnode = newVnode;
}
});
},
};
}
测试
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<!-- <script src=" 上方文件代码引入 ..." > </script> -->
<script>
const App = {
data: reactive({
count: 0,
}),
render: function () {
return h("div", null, [
h("h2", null, `当前计数:${this.data.count}`),
h(
"button",
{
onclick: () => {
this.data.count++;
},
},
"+1"
),
]);
},
};
createApp(App).mount("#app");
</script>
</body>
</html>
以上是关于Vue源码学习的主要内容,如果未能解决你的问题,请参考以下文章