【手写 Vue2.x 源码】第二十三篇 - 依赖收集 - 视图更新部分
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
一前言
上篇主要介绍了依赖收集过程中 dep 和 watcher 关联
利用 js 单线程特性在 Watcher 类中 get 方法即将触发视图更新前
利用全局的类静态属性 Dep.target 记录 Watcher 实例
并且在视图渲染的取值过程中在Object.defineProperty的get方法中让数据的dep记住渲染watcher
从而实现了 dep 与 watcher 相关联只有参与视图渲染的数据发生变化才会触发视图的更新
本篇继续依赖收集的视图更新部分
二实现视图更新逻辑
1.查重 watcher
问题同一数据在视图中多次使用会怎样
按照当前逻辑同一数据在一个视图中被多次使用时相同 watcher 会在 dep 中多次保存
<div id="app">
<li>{{name}}</li>
<li>{{name}}</li>
<li>{{name}}</li>
</div>
这时 name 的 dep 中会保存三个相同的渲染 watcher
所以watcher 需要进行查重
同 Dep 类的做法给 Watcher 添加一个 id
每次 new Watcher 是 id 自增以此作为标记对 watcher 实例进行查重
let id = 0;
class Watcher {
constructor(vm, fn, cb, options){
this.vm = vm;
this.fn = fn;
this.cb = cb;
this.options = options;
this.id = id++; // watcher 唯一标记
this.getter = fn;
this.get();
}
get(){
Dep.target = this;
this.getter();
Dep.target = null;
}
}
export default Watcher
2. 让 watcher 也记住 dep
前面让数据的dep记住了渲染 watcher
同样的watcher 也有必要记住 dep
let id = 0;
class Dep {
constructor(){
this.id = id++;
this.subs = [];
}
// 让 watcher 记住 dep查重再让 dep 记住 watcher
depend(){
// 相当于 watcher.addDep使当前 watcher 记住 dep
Dep.target.addDep(this);
}
// 让 dep 记住 watcher - 在 watcher 中被调用
addSub(watcher){
this.subs.push(watcher);
}
}
Dep.target = null; // 静态属性用于记录当前 watcher
export default Dep;
为什么要这样实现
如果要互相记住watcher 中要对 dep 做查重dep 中也要对 watcher 做查重
用这种方法使 dep 和 watcher 关联在一起后只要判断一次就可以了
import Dep from "./dep";
let id = 0;
class Watcher {
constructor(vm, fn, cb, options){
this.vm = vm;
this.fn = fn;
this.cb = cb;
this.options = options;
this.id = id++;
this.depsId = new Set(); // 用于当前 watcher 保存 dep 实例的唯一id
this.deps = []; // 用于当前 watcher 保存 dep 实例
this.getter = fn;
this.get();
}
addDep(dep){
let did = dep.id;
// dep 查重
if(!this.depsId.has(did)){
// 让 watcher 记住 dep
this.depsId.add(did);
this.deps.push(dep);
// 让 dep 也记住 watcher
dep.addSub(this);
}
}
get(){
Dep.target = this;
this.getter();
Dep.target = null;
}
}
export default Watcher;
这种实现方式会让 dep 和 watcher 保持一种相对的关系
如果 watcher 中存过 dep那么 dep 中一定存过 watcher
如果 watcher 中没存过 dep那么 dep 中一定没存过 watcher
所以只需要判断一次就能完成 dep 和 watcher 的查重
3. 数据改变触发视图更新
当视图更新时会进入 Object.defineProperty 的 set 方法
需要在 set 方法中通知 dep 中所有收集的 watcher执行视图更新方法
// src/observe/index.js#defineReactive
function defineReactive(obj, key, value) {
observe(value);
let dep = new Dep(); // 为每个属性添加一个 dep
Object.defineProperty(obj, key, {
get() {
if(Dep.target){
dep.depend();
}
return value;
},
set(newValue) {
if (newValue === value) return
observe(newValue);
value = newValue;
dep.notify(); // 通知当前 dep 中收集的所有 watcher 依次执行视图更新
}
})
}
4. Dep 中添加 notify 方法
let id = 0;
class Dep {
constructor(){
this.id = id++;
this.subs = [];
}
depend(){
Dep.target.addDep(this);
}
addSub(watcher){
this.subs.push(watcher);
}
// dep 中收集的全部 watcher 依次执行更新方法 update
notify(){
this.subs.forEach(watcher => watcher.update())
}
}
Dep.target = null;
export default Dep;
5. Watcher 中添加 update 方法
import Dep from "./dep";
let id = 0;
class Watcher {
constructor(vm, fn, cb, options){
this.vm = vm;
this.fn = fn;
this.cb = cb;
this.options = options;
this.id = id++;
this.depsId = new Set();
this.deps = [];
this.getter = fn;
this.get();
}
addDep(dep){
let did = dep.id;
if(!this.depsId.has(did)){
this.depsId.add(did);
this.deps.push(dep);
dep.addSub(this);
}
}
get(){
Dep.target = this;
this.getter();
Dep.target = null;
}
// 执行视图渲染逻辑
update(){
this.get();
}
}
export default Watcher;
6. 问题
多次频繁更新同一个数据会使视图频繁进行重新渲染操作
let vm = new Vue({
el: '#app',
data() {
return { name: "Brave" , age: 123}
}
});
vm.name = "Brave Wang";
vm.name = "Brave";
vm.name = "Brave Wang";
vm.name = "Brave";
vm.name = "Brave Wang";
vm.name = "Brave";
name的值变化了 6 次但最终其实没有变化还是 Brave,
这里就需要改为做异步更新的机制
三结尾
本篇介绍了 Vue依赖收集的视图更新部分主要涉及以下几点
视图初始化时
- render方法中会进行取值操作进入 Object.defineProperty 的 get 方法
- get 方法中为数据添加 dep并记录当前的渲染 watcher
- 记录方式watcher查重并记住 depdep 再记住 watcher
数据更新时
- 当数据发生改,会进入 Object.defineProperty 的 set 方法
- 在 set 方法中使 dep 中收集的全部 watcher 执行视图渲染操作 watcher.get()
- 在视图渲染前this.getter方法执行前,通过 dep.target 记录当前的渲染 watcher
- 重复视图初始化流程
下一篇Vue 异步更新
维护日志
20210802修改文章摘要