一,前言
上篇,主要介绍了 Vue 依赖收集的过程分析
本篇,Vue 依赖收集的实现
二,Watcher 部分
1,watcher 的本质
之前分析可知:
vm._render:调用 render 方法
vm._update:将虚拟节点更新到页面上
本质上,vm._update(vm._render())
就可以触发视图的更新
let vm = new Vue({
el: '#app',
data() {
return { name: "Brave" , age: 123}
}
});
vm.name = "Brave Wang"; // 数据改变
vm._update(vm._render()); // 视图更新
复制代码
在 Vue 中,数据更新:
每个数据有一个 dep 属性:记录使用该数据的组件或页面的视图渲染函数 watcher
当数据发生变化时,dep 属性中存放的多个 watcher 将会被通知,这里是观察者模式
这里的 watcher 就相当于vm._update(vm._render())
2,抽取视图更新逻辑 watcher
将视图渲染逻辑抽取成为可调用函数:
export function mountComponent(vm) {
// 抽取成为一个可被调用的函数
let updateComponent = ()=>{
vm._update(vm._render());
}
updateComponent();
}
复制代码
最终的目标是:让updateComponent
方法通过 watcher 被调用
3,创建 Watcher 类
数据改变,视图更新,所以 watcher 应所属于响应式模块
创建 watcher 类:
// src/observe/watcher.js
class Watcher {
constructor(vm, fn, cb, options){
this.vm = vm;
this.fn = fn;
this.cb = cb;
this.options = options;
this.getter = fn; // fn 为页面渲染逻辑
this.get();
}
get(){
this.getter(); // 调用页面渲染逻辑
}
}
export default Watcher;
复制代码
将页面更新逻辑 updateComponent 注入到 Watcher 类中,
再考虑如何通过 watcher 调用页面更新方法 updateComponent
export function mountComponent(vm) {
let updateComponent = ()=>{
vm._update(vm._render());
}
// 渲染 watcher :每个组件都有一个 watcher
new Watcher(vm, updateComponent, ()=>{
console.log('Watcher-update')
},true)
}
复制代码
4,依赖收集的必要性
数据改变时,会被劫持进入 Object.defineProperty 的 set 方法
那么,如果在此时调用了视图更新逻辑,是不是就可以做到“数据变化,视图更新”了?
// src/observe/index.js#defineReactive
Object.defineProperty(obj, key, {
get() {
return value;
},
set(newValue) {
if (newValue === value) return
vm._update(vm._render()); // 当数据变化时,触发视图更新
observe(newValue);
value = newValue;
}
})
复制代码
这样做,虽然可以实现视图的更新
但是,有一个严重的问题:
所以,在视图渲染过程中,被使用的数据需要被记录下来,并且只针对这些数据的变化触发视图更新
这就需要做依赖收集,需要创建为属性创建 dep 用来收集渲染 watcher
三,Dep 部分
1,创建 Dep 类
前面说过:
所以,Dep 类中要有一个添加 watcher 的方法;Watcher 类中 也要有一个添加 dep 的方法;
当数据变化时,通知数据 dep 属性中的所有 watcher 执行视图更新,应用了观察者模式
为了标识 Dep 的唯一性,每次 new Dep 时添加一个唯一 id;
// src/observe/dep.js
let id = 0;
class Dep {
constructor(){
this.id = id++;
this.subs = [];
}
// 保存数据的渲染 watcher
depend(){
this.subs.push(Dep.target)
}
}
Dep.target = null; // 静态属性
export default Dep
复制代码
2,为属性添加 dep 属性
Object.defineProperty 时会为每个数据添加属性,在此时为属性添加 dep:
function defineReactive(obj, key, value) {
observe(value);
let dep = new Dep(); // 为每个属性添加一个 dep
Object.defineProperty(obj, key, {
get() {
return value;
},
set(newValue) {
if (newValue === value) return
observe(newValue);
value = newValue;
}
})
}
复制代码
当视图渲染时,会走 Watcher 中的 get 方法,即vm._update(vm._render())
此时,利用 JS 的单线程特性,在即将进行渲染前,记录当前渲染 watcher
class Watcher {
constructor(vm, fn, cb, options){
this.vm = vm;
this.fn = fn;
this.cb = cb;
this.options = options;
this.getter = fn;
this.get();
}
get(){
Dep.target = this; // 在触发视图渲染前,将 watcher 记录到 Dep.target 上
this.getter(); // 调用页面渲染逻辑
Dep.target = null; // 渲染完成后,清除 Watcher 记录
}
}
export default Watcher
复制代码
在视图渲染的过程中,将会触发数据的取值,如:vm.name
此时,进入 Object.defineProperty 中 get 方法
所以,如果 get 方法中 Dep.target 有值(即当前 watcher),就让数据的 dep 记住渲染 watcher
function defineReactive(obj, key, value) {
observe(value);
let dep = new Dep();
Object.defineProperty(obj, key, {
get() {
if(Dep.target){
dep.depend();
}
return value;
},
set(newValue) {
if (newValue === value) return
observe(newValue);
value = newValue;
}
})
}
复制代码
这样,dep 会记住所有渲染 watcher,未参与视图渲染的数据更新时,不会触发视图更新
四,结尾
本篇, dep 和 watcher 关联
下一篇,视图更新部分
评论