手写Vue2.0源码(三)更新渲染原理

291 阅读3分钟

前言

本文仅以记录自己的学习过程,有其他理解的同学可留言。注意 我学习原理一直保持 28 策略 所谓百分之 20 的代码实现了百分之 80 的功能 所以此系列咱们只关心核心逻辑以及功能的实现


正文

上篇文章介绍了Vue的初次渲染流程,本篇就记录、分析下更新渲染的流程

入口

通过上篇文章可以知道初次渲染的过程,那么在实例化之后更改数据如何更新视图,Vue可没让我们改变数据后,调用this._update(this._render()); 我们学习vue源码的都知道在Object.defineProperty接口中,get时添加依赖,set时通知更新,也就是 deps.forEach(watcherObj => watcherObj.update());下面先看下watcher

Wathcer;vue最核心、代码最美妙的地方

// 之所以说时最核心、代码最美妙的地方,因为Watcher是data computed watch的核心,一个类实现了这几块的功能,有时候学了一块,我就想其他块是如何实现,再去看源码 so beautiful !!!
// 参数解析
  // expOrFn: 路径表达式/computed的函数
  // callback: 回调,渲染wathcer为更新视图的方法 / watch中的方法 
  // options 额外的选项 true代表渲染watcher
    // 渲染wathcer的options: true
    // watch的options:{ user: true, deep: boolean, immediate: boolean}
    // computed的options:{lazy: true, ...}
let id = 0;
class Watcher {
  constructor(vm, exprOrFn, cb, options) {
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    this.cb = cb; 
    this.options = options; //
    this.id = id++; // watcher的唯一标识
    this.deps = []; // 存储 有此订阅者实例的 deps
    this.depsId = new Set();// 存储 有此订阅者实例的 deps的id属性值
    // 如果表达式是一个函数
    if (typeof exprOrFn === "function") {
      this.getter = exprOrFn;
    }
    // 实例化就会默认调用get方法
    this.get();
  }
  get(){
    // pushTarget popTarget是用模拟栈的方式来讲watcher实例暴露出来;
    // 后入先出,防止当前已有watcher实例暴露出来,而被本次操作给覆盖;
    pushTarget(this);
    this.getter();
    popTarget();
  }
  // 属性的let deps = new Dep()还记得吗,deps中存储了订阅者Wathcer实例
  // 于此同时 Watcher实例中也存储了哪些deps存储了我(Watcher实例),是以多对多的形式存在的
  // 这样才可以达到去重的效果
  addDep(dep) {
    if (!this.depsId.has(dep.id)) {
      this.depsId.add(dep.id);
      this.deps.push(dep);
      dep.addSub(this);
    }
  }
  update() {
    this.cb();
    // 这一块还会改,后面会结合 实现异步更新、computed
  }
}

创建渲染Watcher

export function mountComponent(vm, el) {
  //   _update和._render方法都是挂载在Vue原型的方法  类似_init

  // 引入watcher的概念 这里注册一个渲染watcher 执行vm._update(vm._render())方法渲染视图

  let updateComponent = () => {
    console.log("刷新页面");
    vm._update(vm._render());
  };
  // 第二个参数是默认执行的,第三个参数是回调,数据改变时触发set会执行updateComponent更新视图
  new Watcher(vm, updateComponent, updateComponent, true);
}

依赖收集Dep

// src/observer/dep.js

// dep和watcher是多对多的关系
// 每个属性都有自己的dep
let id = 0; //dep实例的唯一标识
export default class Dep {
  constructor() {
    this.id = id++;
    this.subs = []; // 这个是存放watcher的容器
  }
  depend() {
    //   如果当前存在watcher
    if (Dep.target) {
      Dep.target.addDep(this); // 把自身-dep实例存放在watcher里面
    }
  }
  notify() {
    //   依次执行subs里面的watcher更新方法
    this.subs.forEach((watcher) => watcher.update());
  }
  addSub(watcher) {
    //   把watcher加入到自身的subs容器
    this.subs.push(watcher);
  }
}
// 默认Dep.target为null
Dep.target = null;
// 栈结构用来存watcher
const targetStack = [];

export function pushTarget(watcher) {
  targetStack.push(watcher);
  Dep.target = watcher; // Dep.target指向当前watcher
}
export function popTarget() {
  targetStack.pop(); // 当前watcher出栈 拿到上一个watcher
  Dep.target = targetStack[targetStack.length - 1];
}

思维导图

手写vue2.0原理.png

如果觉得本文对你有帮助,记得点赞、收藏、评论,十分感谢!