这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战
前文已经将Vue
的初次渲染流程完成,浏览器中可以将数据进行展示,在日常开发过程中数据是不断变化的,数据变化之后如何驱动视图将最新数据更新至浏览器呢?
初始化Vue
,定义数据
const vm = new Vue({
el: '#app',
data() {
return {
name: 'nordon'
}
}
})
初次渲染流程核心
vm._update(vm._render());
此时修改数据且手动调用初次渲染流程
vm.name = 'wy';
vm._update(vm._render());
页面也是可以将新的数据渲染至浏览器中,这样就会导致一个问题,频繁修改数据就需要自己不断的调用vm._update(vm._render());
,这个过程明显是不符合期望的,我们所期望的是当数据变化时,可以自动的触发渲染Watcher
,驱动浏览器渲染最新的数据,这样才符合数据驱动视图的理念
每个组件都有自己的渲染Watcher
,其作为数据和视图的枢纽存在,当数据变化时需要触发渲染Watcher
,进而驱动视图更新,渲染Watcher
生成的地方
export function mountComponent(vm, el) {
// 渲染页面
// 无论渲染还是更新 都会执行
let updateComponent = () => {
// vm._render() 返回的是虚拟DOM
vm._update(vm._render());
};
// 渲染 watcher, 每一个组件都有一个watcher
// true 表示他是一个渲染watcher
new Watcher(vm, updateComponent, () => {}, true);
}
在初始化new Watcher
时会自动执行updateComponent
进行页面渲染或者更新
class Watcher {
constructor(vm, exprOrFn, callback, options) {
this.getter = exprOrFn; // 将内部传过来的回调函数 放到 getter 属性上
this.get(); // 调用get方法, 会让渲染 watcher 执行
}
get() {
this.getter(); // 渲染 watcher 执行
}
}
可以看到初始化会先将exprOrFn
存储,在get
函数中执行,现在需要在渲染Watcher
执行前将其存储下来,当渲染完成之后再将其移除,这个过程就是依赖收集将get
函数改造为
class Watcher {
get() {
pushTarget(this); // 存储watcher, Dep.target
this.getter(); // 渲染 watcher 执行
popTarget(); // 移除 watcher
}
}
将两个函数实现
// 将watcher 保留起来
let stack = [];
/**
* 存储 渲染Watcher
*/
export function pushTarget(watcher) {
Dep.target = watcher;
stack.push(watcher);
}
/**
* 移除 渲染Watcher
*/
export function popTarget() {
stack.pop();
Dep.target = stack[stack.length - 1];
}
收集依赖使用Dep
进行,代码如下
let id = 0;
/**
* Watcher 和 Dep 是多对多的关系
*/
export default class Dep {
constructor() {
this.id = id++;
this.subs = [] // name: [watcher, watcher]
}
depend() {
// 让这个 watcher 记住当前的 dep
// 如果 watcher 没有存过 dep, dep 肯定不能存过watcher
Dep.target.addDep(this)
}
// 通知 watcher 更新
notify() {
this.subs.forEach(watcher => watcher.update())
}
addSub(watcher) {
this.subs.push(watcher)
}
}
在渲染Wtcher
渲染页面时,需要进行取值操作,会触发数据劫持的get
方法,这个时候需要进行依赖收集
function defineReactive(data, key, value) {
// 这个dep 是给对象使用的, 数组是不能使用的
let dep = new Dep();
Object.defineProperty(data, key,
get() {
/**
* 每个属性 都对应着 自己的 watcher,
* 需要给每个属性都增加 watcher
*/
if (Dep.target) {
// 有值 代表渲染watcher 已经放上去了
// 如果当前存在 watcher, 将watcher 和 dep 建立一个双向的关系
dep.depend(); // 我要将 watcher 存起来
}
return value;
},
});
}
梳理大致流程:
创建渲染Watcher
时会执行Watch
的get
函数,其内部做了三件事情:存储渲染Watcher
、执行updateComponent
函数和移除渲染Watcher
,在进行渲染的过程中存在取值操作,触发Object.defineProperty
的 get
函数,此时Dep.target
值为当前的渲染Watcher
,调用dep.depend()
将依赖进行收集
当数据变化时,只需要将之前数据对应的watcher
触发,便可以触发渲染更新视图