1. Revisión y Antecedentes
El ensayo anterior analiza en createElm
detalle la lógica de este método, que se basa en VNode
la creación de elementos reales, que contienen dos escenarios:
- Si
VNode
es un componente personalizado, llame alcreateComponent
método para procesar; - Si es un elemento normal,
nodeOps.createElement
creando unHTML
elemento nativo;
Al final del artículo anterior, new Vue
ordené insert
todo el flujo de la pila de ejecución del proceso de a a Este proceso es todo el proceso Vue
de new Vue
renderizado a la página.
Pero Vue
es un sistema receptivo. El proceso anterior solo se completó a la mitad, y la mitad restante es que cuando los datos receptivos cambian, Vue
el renderizador watcher
recibe una notificación para activar el re-renderizado, que es lo que todos dicen a menudo patch 阶段
.
Por su Vue
propio diseño interno, siempre que sea VNode
el 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 Vue
el código fuente , así que hábilmente lo dividí en dos procesos y lo nombré;
- Al primer renderizado lo llamamos
初次渲染
, es decir, al anterior挂载阶段
, la primera vez que se模板
convierte enDOM
Renderizado a la página; - La última parte se llama
patch 阶段
, la última etapa es cuando la actualización de datos de respuesta activa la reproducción, y tu favoritadom diff
es 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 patch
etapa , agregamos un botón a la plantilla, los button#btn
botones modificarán el atributo:点击事件
handler
data.forProp.a
forPatchComputed
Esta propiedad calculada depende dedata.forProp
;<some-com />
someKey
Los 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.forProp
se modifica, ahora hay tres watcher
para activar una actualización:
forPatchComputed
Correspondiente a la propiedad calculadawatcher
, tenga en cuenta que la propiedad calculada eswatcher
sílazy
;some-com
correspondiente渲染 watcher
;div#app
La 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
Watcher
instancia , ywatcher
se ha visto desde el literal que es el observador (watch
no es la traducción al inglés注视、看
~); - El objeto observado es naturalmente el dato mismo, como
data.forProp
este objeto; - El responsable de la comunicación entre ambos es una
Dep
instancia ,Dep
una dependencia, fíjate que un sustantivo no es un verbo (lo que quiero expresar es被 watcher 依赖
que si依赖别人
se ); cada dato responsivodep
lo tiene,dep
el cual se encarga de recopilar y utilizar estos datoswatcher
, y luego los datos cambian Distribuya una notificación para que tomewatcher
medidas ;
3.1 Revisar la implementación reactiva
Hay tres componentes en la capacidad de respuesta de los datos:
- 在初始化响应式数据的时候将
data
通过defineReactive
方法(核心是Object.defineProperty
)将data.forProp
变成getter
和setter
,当被访问时通过getter
被触发收集依赖,当被需改时触发setter
通知依赖这个数据的watcher
们更新; - 初始化响应式数据时处理
computed
的逻辑:- 2.1 给每个计算属性创建一个
watcher
,而创建Watcher
类的实例时传入的expOrFn
即要求值的函数,就是定义计算属性时声明的方法例如上面的例子:forPatchComputed () { return this.forProp.a + 'reject 996!!!' }
; - 2.2 计算属性是
lazy watcher
,即这个计算属性被访问的时候才会求值; - 2.3 什么时候求值呢?被访问到时候,
forPatchComputed
是根实例的模板对应的render 函数
执行的时候就会拿forPatchComputed
对应的值,这时求值;
- 2.1 给每个计算属性创建一个
- 修改
data.forProp.a
触发setter
,而前面getter
已经知道有三个watcher
依赖了这个forProp
,此时通知他们三个更新;
谁来负责收集依赖 watcher
们和通知 watcher
们更新呢?Dep 类
,在数据响应式初始化的时候给每个数据都创建一个 Dep
的实例,dep.denpend
收集依赖 watcher
,dep.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
:
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
:
- 第一个是 forPatchComputed 这个
计算属性 watcher
,如图
2. 第二个是 div#app
这个根实例模板对应的渲染 watcher
:
- 第三个则是
<some-com />
自定义组件的渲染 watcher
:
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
3.3.2 渲染 watcher 的 update
queueWatcher
是一个重点,我们为它单独开一篇;
四、总结
本篇小作文作为 patch
阶段的第一篇主要做了以下工作:
- 重新修改
test.html
加入了可以修改响应式数据的button#btn
元素,以及绑定点击事件修改data.forProp.a
; - 重新梳理了完整的响应式流程,包含依赖收集、修改数据、派发更新的过程;并且明确了
Watcher
、Dep
以及响应式数据间的依赖和被依赖关系以及三者协作过程; - 通过修改
this.forProp.a
进入到了dep.notify()
,接着看到了作为计算属性
的lazy watcher
和普通 watcher
在watcher.update()
方法中的不同处理方式:-
3.1 lazy watcher 把 this.dirty 置为 true;这就可以使得计算属性的缓存失效,当计算属性再次被访问到的时候,就会重新求值,这个过程我们在说 Watcher 的时候详细介绍过 computed 的缓存原理;
-
3.2 普通
watcher
包括渲染 watcher
和用户 watcher
都会执行queueWatcher
方法进行异步的队列更新;
-