续上文: Vue3源码学习之旅(1)–自己实现一个简单的渲染系统-render/h/patch函数
依赖收集系统
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify() {
this.subscribers.forEach(effect => {
effect();
})
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect()
activeEffect = null;
}
const targetMap = new WeakMap();
function getDep(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
数据劫持
响应式系统Vue2实现
**
* vue2对raw进行数据劫持
* @param {*} 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;
}
响应式系统Vue3实现
**
* vue3对raw进行数据劫持
* @param {*} 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({ name: '毛毛', age: 21 })
watchEffect(() => {
console.log(info.age * 2);
})
info.age++;
let obj = reactive({name:'hah ',counter:1});
watchEffect(()=>{
console.log("name:",obj.name);
})
watchEffect(()=>{
console.log("counter:",obj.counter);
})
obj.counter++
为什么Vue3选择Proxy呢?
Object.definedProperty 是劫持对象的属性时,如果新增元素:
那么Vue2需要再次 调用definedProperty,而 Proxy 劫持的是整个对象,不需要做特殊处理;
修改对象的不同:
- 使用 defineProperty 时,我们修改原来的 obj 对象就可以触发拦截;
- 而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截;
Proxy 能观察的类型比 defineProperty 更丰富
- has:in操作符的捕获器;
- deleteProperty:delete 操作符的捕捉器;
- 等等其他操作;
框架外层API设计
这样我们就知道了,从框架的层面来说,我们需要 有两部分内容:
function createApp(rootComponent) {
return {
mount(selector) {
const container = document.querySelector(selector);
let isMounted = false;
let oldVNode = null;
watchEffect(() => {
if (!isMounted) {
oldVNode = rootComponent.render();
mount(oldVNode, container);
isMounted = true;
} else {
const newVNode = rootComponent.render()
patch(oldVNode, newVNode);
oldVNode = newVNode;
}
})
}
}
}
测试
const App = {
data: reactive({
counter: 0
}),
render() {
return h("div", { style: "color:red" }, [
h("h2", null, `当前计数:${this.data.counter}`),
h("button", {
onClick: () => {
this.data.counter++
}
}, "+1")
])
}
}
const app = createApp(App)
app.mount("#app")
|