Antecedentes
Considerando que o vue3 ainda não foi lançado oficialmente, e algumas das ideias de implementação no vue2 ainda são muito valiosas para referência, portanto, essa explicação de princípios ainda é vue2, espero que você tenha inspiração ~
Implementação de sistema responsivo
O Vue.js é uma estrutura MVVM. O modelo de dados é apenas objetos JavaScript comuns, mas quando esses objetos são manipulados, eles podem afetar as visualizações correspondentes. Sua principal implementação é o "sistema responsivo".
No vue 2.0, ele se baseia na Object.defineProperty
implementação do "sistema responsivo". Vue3 é baseado na Proxy/Reflect
realização, análise detalhada do vue3 terá tempo para escrever, este artigo é sobre a realização do vue2.
Envolve principalmente atributos:
-
enumerável, se a propriedade pode ser enumerada, o padrão é false.
-
configurável, se a propriedade pode ser modificada ou excluída, o padrão é false.
-
get, o método para obter o atributo
-
set, o método de configuração de propriedades.
O princípio básico da capacidade de resposta é processar os dados das opções no construtor Vue. Ou seja, quando a instância do vue é inicializada, cada propriedade de dados, objetos e outros objetos é definida uma vez por Object.defineProperty.Quando os dados são configurados, execute algumas operações para alterar a visualização correspondente.
class Vue {
/* Vue构造类 */
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
function observer (value) {
if (!value || (typeof value !== 'object')) {
return;
}
Object.keys(value).forEach((key) => {
defineReactive(value, key, value[key]);
});
}
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true, /* 属性可枚举 */
configurable: true, /* 属性可被修改或删除 */
get: function reactiveGetter () {
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
cb(newVal);
}
});
}
Em aplicações práticas, vários sistemas são extremamente complicados. Suponha que agora tenhamos um objeto global, podemos usá-lo em vários objetos do Vue para exibição. Ou talvez os dados gravados nos dados não sejam aplicados à visualização e a atualização da visualização no momento seja supérflua. Isso depende do processo de coleta.
Coleção dependente
A chamada coleta dependente é coletar o local onde um dado é usado.Quando esses dados mudam, ele notifica cada local para realizar a operação correspondente. O modo básico de "assinante" no VUE é o seguinte:
exportdefaultclass Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
//依赖收集,有需要才添加订阅
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Com os assinantes, vamos dar uma olhada na implementação do Watcher. O código-fonte Watcher é mais lógico e o modelo simplificado é o seguinte
class Watcher{
constructor(vm,expOrFn,cb,options){
//传进来的对象 例如Vue
this.vm = vm
//在Vue中cb是更新视图的核心,调用diff并更新视图的过程
this.cb = cb
//收集Deps,用于移除监听
this.newDeps = []
this.getter = expOrFn
//设置Dep.target的值,依赖收集时的watcher对象
this.value =this.get()
}
get(){
//设置Dep.target值,用以依赖收集
pushTarget(this)
const vm = this.vm
let value = this.getter.call(vm, vm)
return value
}
//添加依赖
addDep (dep) {
// 这里简单处理,在Vue中做了重复筛选,即依赖只收集一次,不重复收集依赖
this.newDeps.push(dep)
dep.addSub(this)
}
//更新
update () {
this.run()
}
//更新视图
run(){
//这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
}
}
defineReactive detalhada lógica
exportfunction defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
所以响应式原理就是,我们通过递归遍历,把vue实例中data里面定义的数据,用defineReactive(Object.defineProperty)重新定义。每个数据内新建一个Dep实例,闭包中包含了这个 Dep 类的实例,用来收集 Watcher 对象。在对象被「读」的时候,会触发 reactiveGetter 函数把当前的 Watcher 对象(存放在 Dep.target 中)收集到 Dep 类中去。之后如果当该对象被「写」的时候,则会触发 reactiveSetter 方法,通知 Dep 类调用 notify 来触发所有 Watcher 对象的 update 方法更新对应视图。
O nascimento de Watcher
No Vue, existem 4 situações que irão gerar o Watcher:
-
O inspetor no objeto de instância do Vue, observa os dados raiz e renderiza novamente o componente quando houver uma alteração updateComponent = () => {vm._update (vm._render (), hydrating)} vm._watcher = new Watcher (vm, updateComponent, noop)
-
O inspetor criado pelo usuário com o atributo watch no objeto vue
-
O atributo calculado criado pelo usuário no objeto vue é essencialmente um observador
-
Observador criado pelo usuário usando vm. $ Watch
O Wathcer aumentará ou diminuirá e pode ser adicionado ao renderizar. Portanto, deve haver um agendamento para agendar o Observador. Alguns códigos principais são os seguintes:
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
O papel do Cronograma:
-
Desduplicação, cada Observador possui um ID exclusivo. Primeiro, se o ID já estiver na fila, pule-o, não há necessidade de repetir a execução.Se o ID não estiver na fila, depende se a fila está sendo executada. Se não estiver em execução, a fila é executada no próximo intervalo de tempo, portanto, a fila é sempre executada de forma assíncrona.
-
A classificação é realizada na ordem de análise e renderização, ou seja, o Watcher é executado primeiro. O ID no Watcher aumenta automaticamente, o ID criado primeiro é menor que o ID criado posteriormente. Portanto, haverá as seguintes regras:
2.1 Os componentes podem ser aninhados e a análise deve analisar primeiro o componente pai e depois o componente filho. Portanto, o ID do componente pai é menor que o componente filho.
2.2 O Watcher criado pelo usuário será analisado antes do criado durante a renderização. Portanto, o ID do Watcher criado pelo usuário é menor que o criado durante a renderização.
-
Excluir observador Se o observador de um componente estiver na fila e seu componente pai for excluído, esse observador também deverá ser excluído neste momento.
-
Durante a execução da fila, é armazenada uma circular de objeto, que contém o número de execuções de cada observador. Se algum observador executar mais do que o número de vezes definido por MAX_UPDATE_COUNT, ele será considerado um loop infinito e não será mais executado. O padrão é 100 vezes.
总之,调用的作用就是管理 Watcher。
Suplemento
Como usar Object.defineProperty para redefinir o objeto de matriz no VUE2? Por que a exibição não muda responsivamente quando modificamos diretamente um item nos dados (arr [3] = 4)
A resposta é que a capacidade de resposta da matriz não está completa e o VUE reescreveu apenas métodos limitados. A lógica de reescrita é a seguinte:
const arrayProto = Array.prototype
exportconst arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case'push':
case'unshift':
inserted = args
break
case'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})