Skip to content

页面更新

javascript
// 页面的初始化渲染及后续页面响应式更新都是通过 updateComponent 实现的,当数据变化时,
// 重新调用 updateComponent 将 render 函数 => VNode => DOM,由于 render函数中
// 的变量会去 data 中取,所以 data 改变后就会生成不同的 vnode,从而生成不同的 dom,
// 实现了数据变化引起视图变化。我们需要实现当 data 变化时重新调用 updateComponent,
// 通过观察者模式实现,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,
// 所有依赖于它的对象都将得到通知。

// mountComponent 内部最后 new Watcher,Watcher 类构造器中接收
// vm、expOrFn、cb,options 四个参数,对于更新视图的 render watcher 来说
// expOrFn 就是 updateComponent,options 则是 true,被用来标识是 render watcher
// 构造器中 expOrFn 会被挂载到 this.getter 上,原型中定义 get 方法,get 内部会执行
// this.getter 用于初始化渲染及响应式更新,构造器最后会执行 this.get,即
// updateComponent 实现页面的初始化渲染,原型中有个 update 方法,后续的响应式更新
// 就是在 update 方法中执行 this.get 方法从而执行 getter,即 updateComponent 
// 实现视图更新。当然实际上,由于要实现异步更新,update 中不直接执行 this.get,而是
// 执行 queueWatcher,queueWatcher 中让每个 watcher 执行 this.run,this.run 中执行
// this.get。
// 如何实现 data 变化时就调用 render watcher 上的 update 方法?让每个属性收集用到它的
// render watcher,属性变化时,通知 render watcher 调用 this.update。

// dep 和 watcher 其实是互相收集的,至于 watcher 为什么要收集 dep,后面再讨论。
// Dep 类的构造器中会生成 id 用于标识 dep,让 watcher 收集 deps 时能
// 去重,subs 用于记录这个 dep 收集的 watchers
// Watcher 类的构造器中也会生成 id 用于标识 watcher,deps 用于记录这个 watcher
// 收集的 deps,depIds 是一个 Set 集合,用于实现 dep 去重。

// 互相收集的具体过程就是 Dep 类上添加一个属性 target 用于记录当前 watcher,初始化渲
// 染时会调用 get,get 内部在调用 this.getter 之前,通过调用 pushTarget(this) 让
// Dep.target 记录当前 render watcher,之后执行 this.getter进行初始化渲染,初始化
// 渲染时会去 data 中取值 => _s(name),由于我们劫持了 data 的所有层次的对象的属性,
// 在 Object.defineProperty 的 get 中调用 dep.depend 收集让 dep 收集render watcher,
// Dep 实例在哪里生成?由于每个属性都是一个 dep,在 defineReactive中
// const dep = new Dep() 即可,dep.depend 并不直接收集 render watcher,而是内部
// 调用 Dep.target.addDep(this) 先让 watcher 收集 dep,Dep.target 此时就是
// render watcher,this 就是 dep,addDep 内部会取出 dep.id,如果 Set 中不包含这个
// id 则 this.deps.push(dep)、this.depIds.add(id) 完成render watcher收集dep
// 收集完成后再 dep.addSub(this) 让 dep 收集 render watcher,由于 watcher 已经
// 去重了 dep,dep 收集到的 render watcher 自然不会是重复的,这样就完成了
// dep 和 render watcher 的互相收集,最后在 this.getter 最后通过 popTarget 清除记录。

// 当数据被修改时会触发 Object.defineProperty 中的 set,在 set 中调用 dep.notify,
// 内部会让该 dep 收集的每一个 watcher 都调用update,update 中执行 queueWatcher,
// queueWatcher 中让每个 watcher 执行 this.run,this.run 中执行this.get 重新渲染
// 页面,实现响应式更新。
class Watcher implements DepTarget {
  constructor(
    vm: Component | null,
    expOrFn: string | (() => any),
    cb: Function,
    options?: WatcherOptions | null
  ) {
    this.id = ++uid // uid for batching
    this.deps = [] // watcher 收集的 deps
    this.depIds = new Set() // 保存收集过的 dep 的 id,用于 dep 去重。
    this.getter = expOrFn
    this.value = this.get()
  }

  get() {
    // 记录当前 watcher
    pushTarget(this)
    let value
    const vm = this.vm
    value = this.getter.call(vm, vm)
    // 清除记录
    popTarget()
  }

  // watcher 添加 dep
  addDep(dep: Dep) {
    const id = dep.id
   
    if (!this.depIds.has(id)) {
      this.depIds.add(id)
      this.deps.push(dep)
      dep.addSub(this)
    }
  }

  update() {
    queueWatcher(this)
  }
 
  run() {
    this.get()
  }
}

class Dep {
  constructor() {
    this.id = uid++
    this.subs = [] // dep 收集的 watchers
  }

  // dep 收集 watcher
  addSub(sub: DepTarget) {
    this.subs.push(sub)
  }

  depend(info?: DebuggerEventExtraInfo) {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify(info?: DebuggerEventExtraInfo) {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

function defineReactive(
  obj: object,
  key: string,
  val?: any,
  customSetter?: Function | null,
  shallow?: boolean,
  mock?: boolean
) {
  // 每个属性收集使用它们的 watcher
  const dep = new Dep()

  // 为该属性实现响应式
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // 如果模板外使用数据,不进行收集操作。
      if (Dep.target) {
        // 互相收集
        dep.depend()
      }
    },
    set: function reactiveSetter(newVal) {
      // 通知 render watcher 更新
      dep.notify()
    }
  })

  return dep
}