Qianxi Vue source code-41-patch stage-trigger responsive update

1. Revisión y Antecedentes

El ensayo anterior analiza en createElmdetalle la lógica de este método, que se basa en VNodela creación de elementos reales, que contienen dos escenarios:

  1. Si VNodees un componente personalizado, llame al createComponentmétodo para procesar;
  2. Si es un elemento normal, nodeOps.createElementcreando un HTMLelemento nativo;

Al final del artículo anterior, new Vueordené inserttodo el flujo de la pila de ejecución del proceso de a a Este proceso es todo el proceso Vuede new Vuerenderizado a la página.

Pero Vuees un sistema receptivo. El proceso anterior solo se completó a la mitad, y la mitad restante es que cuando los datos receptivos cambian, Vueel renderizador watcherrecibe una notificación para activar el re-renderizado, que es lo que todos dicen a menudo patch 阶段.

Por su Vuepropio diseño interno, siempre que sea VNodeel proceso de renderizado a la página, se llama patch, y no se divide en dos grandes procesos, aunque el interior se diferencie o se renderice por primera vez, o los datos responsivos sean cambiado y renderizado. Pero esto no es amigable para las personas que son principiantes en Vueel código fuente , así que hábilmente lo dividí en dos procesos y lo nombré;

  1. Al primer renderizado lo llamamos 初次渲染, es decir, al anterior 挂载阶段, la primera vez que se 模板convierte en DOMRenderizado a la página;
  2. La última parte se llama patch 阶段, la última etapa es cuando la actualización de datos de respuesta activa la reproducción, y tu favorita dom diffes la pequeña monada en esta etapa (ella abusa de ti miles de veces y la tratas como tu primer amor);

2. Análisis de procesos + código de muestra

Para adaptarnos a la actualización de datos responsivos en la patchetapa , agregamos un botón a la plantilla, los button#btnbotones modificarán el atributo:点击事件handlerdata.forProp.a

  • forPatchComputedEsta propiedad calculada depende de data.forProp;
  • <some-com />someKeyLos datos prop-bound recibidos por el componente también sondata.forProp

El código test.html es el siguiente:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Vue</title>
</head>
<body>
<div id="app">
 <button id="btn" @click="goForPatch">使 forProp.a++</button>
 <some-com :some-key="forProp"><div slot="namedSlot">SLOT-CONTENT</div></some-com>
 <div>forPatchComputed = {{forPatchComputed}}</div>
 <div class="static-div">静态节点</div>
</div>
<script src="./dist1/vue.js"></script>
<script>
  const sub = {
    template: `
      <div style="color: red;background: #5cb85c;display: inline-block">
    <slot name="namedSlot">slot-fallback-content</slot>
    {{ someKey.a + ' foo' }}
   </div>`,
    props: {
      someKey: {
        type: Object,
        default () {
          return { a: '996-rejected!!' }
       }
     }
   };
  debugger
  new Vue({
    el: '#app',
    data: {
      forProp: {
        a: 100
      }
    },
    methods: {
      goForPatch () {
         this.forProp.a++
      }
    },
    computed: {
       // 计算属性
       forPatchComputed () {
         return this.forProp.a + ' reject 996'
       }
    },
    components: {
      someCom: sub
    }
  })
</script>
</body>
</html>
复制代码

Entonces, cuando data.forPropse modifica, ahora hay tres watcherpara activar una actualización:

  1. forPatchComputedCorrespondiente a la propiedad calculada watcher, tenga en cuenta que la propiedad calculada es watcherlazy;
  2. some-comcorrespondiente 渲染 watcher;
  3. div#appLa plantilla de esta instancia raíz corresponde a 渲染 watcher;

3. Activar actualizaciones receptivas

En un sistema reactivo, es un patrón de observador obvio, que requiere que averigüemos quién es 观察者, quién es el sujeto 观察y quién es responsable de los dos 通信;

  • El observador es la Watcherinstancia , y watcherse ha visto desde el literal que es el observador ( watchno es la traducción al inglés 注视、看~);
  • El objeto observado es naturalmente el dato mismo, como data.forPropeste objeto;
  • El responsable de la comunicación entre ambos es una Depinstancia , Depuna dependencia, fíjate que un sustantivo no es un verbo (lo que quiero expresar es 被 watcher 依赖que si 依赖别人se ); cada dato responsivo deplo tiene, depel cual se encarga de recopilar y utilizar estos datos watcher, y luego los datos cambian Distribuya una notificación para que tome watchermedidas ;

3.1 Revisar la implementación reactiva

Hay tres componentes en la capacidad de respuesta de los datos:

  1. 在初始化响应式数据的时候将 data 通过 defineReactive 方法(核心是 Object.defineProperty)将 data.forProp 变成 gettersetter,当被访问时通过 getter 被触发收集依赖,当被需改时触发 setter 通知依赖这个数据的 watcher 们更新;
  2. 初始化响应式数据时处理 computed 的逻辑:
    • 2.1 给每个计算属性创建一个 watcher,而创建 Watcher 类的实例时传入的 expOrFn 即要求值的函数,就是定义计算属性时声明的方法例如上面的例子:forPatchComputed () { return this.forProp.a + 'reject 996!!!' }
    • 2.2 计算属性是 lazy watcher,即这个计算属性被访问的时候才会求值;
    • 2.3 什么时候求值呢?被访问到时候,forPatchComputed 是根实例的模板对应的 render 函数执行的时候就会拿 forPatchComputed 对应的值,这时求值;
  3. 修改 data.forProp.a 触发 setter,而前面 getter 已经知道有三个 watcher 依赖了这个 forProp,此时通知他们三个更新;

谁来负责收集依赖 watcher 们和通知 watcher 们更新呢?Dep 类,在数据响应式初始化的时候给每个数据都创建一个 Dep 的实例,dep.denpend 收集依赖 watcherdep.notify 通知 watcher 更新。watcher 更新则是通过 watcher.update() 方法实现的;

function defineRective () {
  const dep = new Dep();
  
  Object.defineProperty(target, key {
    get () {
      if (Dep.target) dep.depend()
    }
    set () {
      dep.notify()
    }
  })
}
复制代码

下图是点击 button#btn 后因修改 this.forProp.a 而触发 setter 进入到 setter

image.png

3.2 Dep.prototype.notify

export default class Dep {

  // 把 watcher 放大响应式数据的依赖列表中
  depend () {
    if (Dep.target) {
      // Dep.target 是响应式数据 getter 时设置的,值是 watcher 实例
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()

    // 遍历 dep 中存储的 watcher,执行 watcher.update()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
复制代码

下图是点击按钮后断点调试进入到 dep.notify,你会发现 this.subs 中有三个 watcher

image.png

  1. 第一个是 forPatchComputed 这个计算属性 watcher,如图

image.png 2. 第二个是 div#app 这个根实例模板对应的渲染 watcher

image.png

  1. 第三个则是 <some-com /> 自定义组件的渲染 watcher

image.png

3.3 Wather.prototype.update

export default class Watcher {
  constructor (...) {}
 
  update () {

    if (this.lazy) {
      // 懒执行的 watcher 走这里
      // 将 dirty 置为 true,
      // 就可以让 计算属性对应的 getter 被访问到的时候
      // 再触发重新计算 computed 回调函数的执行结果
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      // 更新时一般都在这里,
      // 将 watcher 放入到 watcher 队列,
      // 然后异步更新队列
      queueWatcher(this)
    }
  }
}
复制代码

3.3.1 计算属性的 update

image.png

3.3.2 渲染 watcher 的 update

image.png

queueWatcher 是一个重点,我们为它单独开一篇;

四、总结

本篇小作文作为 patch 阶段的第一篇主要做了以下工作:

  1. 重新修改 test.html 加入了可以修改响应式数据的 button#btn 元素,以及绑定点击事件修改 data.forProp.a
  2. 重新梳理了完整的响应式流程,包含依赖收集、修改数据、派发更新的过程;并且明确了 WatcherDep以及响应式数据间的依赖和被依赖关系以及三者协作过程;
  3. 通过修改 this.forProp.a 进入到了 dep.notify(),接着看到了作为计算属性lazy watcher普通 watcherwatcher.update() 方法中的不同处理方式:
    • 3.1 lazy watcher 把 this.dirty 置为 true;这就可以使得计算属性的缓存失效,当计算属性再次被访问到的时候,就会重新求值,这个过程我们在说 Watcher 的时候详细介绍过 computed 的缓存原理;

    • 3.2 普通 watcher 包括渲染 watcher用户 watcher 都会执行 queueWatcher 方法进行异步的队列更新;

Supongo que te gusta

Origin juejin.im/post/7078897921801846798
Recomendado
Clasificación