Princípio de dados responsivos Vue3 manuscritos


Prefácio

Queremos processar os dados de um objeto para alterar o dom. Mas como alterar os dados de um objeto?

A ligação de dados bidirecional do vue2 é implementada usando uma API do ES5, Object.defineProperty() para sequestrar dados e combiná-los com o modo publicar-assinar.

vue3 usa ProxyAPI do ES6 para fazer proxy de dados. Cada objeto é encapsulado com uma camada de Proxy por meio da função reactive() e as alterações nos atributos são monitoradas por meio do Proxy para monitorar os dados.

Comparado com a versão vue2, aqui estão as vantagens de usar proxy:

    1. defineProperty só pode monitorar uma determinada propriedade e não pode monitorar o objeto inteiro. Você pode omitir for...in...,closes, etc.para melhorar a eficiência (basta vincular o objeto inteiro diretamente).
    1. Para monitorar um array, você não precisa mais realizar operações específicas no array separadamente. O proxy pode interceptar diretamente operações em todos os dados do tipo de objeto, suportando perfeitamente o monitoramento de arrays.

Se quisermos saber como implementar dados responsivos Vue3, precisamos conhecer o conceito de proxy.


1. O que é procuração?

Proxy é uma tecnologia de rede de computadores que atua como intermediário entre o cliente e o servidor, encaminhando solicitações e respostas da rede. Quando um cliente envia uma solicitação, o servidor proxy recebe e encaminha a solicitação ao servidor de destino e, em seguida, encaminha a resposta retornada pelo servidor ao cliente.

É equivalente a uma estrela e um agente. Se você quiser encontrar uma estrela para fazer algo, você precisa encontrar seu agente. Os assuntos da estrela são todos tratados pelo agente. A estrela é o objeto de origem e o agente é equivalente ao proxy.

Proxy é usado para criar um proxy para um objeto para implementar a interceptação e personalização de operações básicas (como pesquisa de atributos, atribuição, enumeração, chamada de função, etc.).

1.1 Uso básico de proxy

// 定义一个源对象
let obj = {
    
    
	name: 'qx',
	age: 24
}
// 实现一个Proxy,传入要代理的对象和get和set方法
const proxy =  new Proxy(obj, {
    
    
	// get中返回代理对象的,target代表源对象(也是上面的obj),key代表obj中每个属性
	get(target, key) {
    
    
		return target[key];
	},
	// set中返回代理对象的,target代表源对象(也是上面的obj),key代表obj中每个属性,value是修改的新值
	set(target, key, value) {
    
    
		target[key] = value
		return true
	}
})
console.log(proxy)

obj.name = 'xqx'
// 现在打印的是修改后的proxy,看看会变成什么样?  已经修改好了
console.log(proxy)

Insira a descrição da imagem aqui

2. Implemente a função reativa mais básica

reativo é usado para criar um objeto reativo que pode conter várias propriedades e propriedades aninhadas. Ao usar reativo para criar um objeto reativo, o objeto retornado é um objeto proxy que possui as mesmas propriedades do objeto original, e quaisquer alterações nas propriedades do objeto proxy acionarão uma nova renderização do componente.

Agora que já sabemos que reativo é uma função e retorna um objeto proxy, vamos primeiro configurar o framework mais básico.

function reactive(data) {
    
    
	return new Proxy(data, {
    
    
		get(target, key) {
    
    
			return target[key];
		},
		set(target, key, value) {
    
    
			target[key] = value
			return true
		}
	})
}

Parece ter sido concluído, mas quando um não-objeto é passado, um erro é relatado.

Avise que esse objeto é do tipo objeto, como uma matriz, e é apenas {}.

const arr = true;
console.log(reactive(arr))

Insira a descrição da imagem aqui
Ele avisa que o proxy precisa passar um objeto, portanto, ele precisa primeiro determinar se é um objeto.

function reactive(data) {
    
    
	//判断是不是对象,null也是object要排除
	if(typeof data === Object && data !== null) return 
	
	return new Proxy(data, {
    
    
		get(target, key) {
    
    
			return target[key];
		},
		set(target, key, value) {
    
    
			target[key] = value
			return true
		}
	})
}

3. Implemente um sistema responsivo básico

Sabemos que processar dados serve para atualizar a visão, mas um sistema não pode prescindir de funções de efeitos colaterais.

Funções de efeitos colaterais, como o nome sugere, funções que produzem efeitos colaterais são chamadas de funções de efeitos colaterais. Em termos leigos, esta função pode afetar outras variáveis.

Vejamos a função de efeito colateral mais básica

<div id="app"></div>
<script>
	let obj = {
    
    
		name: 'qx'
	}
	function effect(){
    
    
		app.innerText = obj.name
	}
	
	effect()
</script>

Agora precisamos completar um sistema responsivo básico através da função reativa anterior

<body>
	<div id="app"></div>
	<script>
		let obj = {
    
    
			name: 'qx',
			age: 24
		}
		function reactive(data) {
    
    
			if(typeof data === Object && data !== null) return 
			return new Proxy(data, {
    
    
				get(target, key) {
    
    
					return target[key];
				},
				set(target, key, value) {
    
    
					target[key] = value
					return true
				}
			})
		}
		const state = reactive({
    
    name:'xqx'});
		
		function effect(){
    
    
			app.innerText = state.name
		}
		
		effect()
	</script>
</body>

Insira a descrição da imagem aqui
Até agora, apareceu um sistema responsivo mais básico

4. Melhore o sistema responsivo básico

Se várias funções de efeito colateral se referirem a uma variável ao mesmo tempo, precisamos que cada função de efeito colateral seja executada quando a variável mudar.

4.1 Execute cada função de efeito colateral

Você pode colocar várias funções de efeito colateral em uma lista e percorrer cada função de efeito colateral toda vez que operar no objeto e executar o método set no proxy.

<body>
	<div id="app"></div>
	<script>
		let obj = {
    
    name: 'qx'}
		let effectBucket = [];
		
		function reactive(data) {
    
    
			if(typeof data === Object && data !== null) return 
			return new Proxy(data, {
    
    
				get(target, key) {
    
    
					return target[key];
				},
				set(target, key, value) {
    
    
					target[key] = value
					effectBucket.forEach(fn=>fn())
					return true
				}
			})
		}
		
		const state = reactive({
    
    name:'xqx'});
		
		function effect(){
    
    
			app.innerText = state.name
			console.log('副作用函数1被执行')
		}
		effectBucket.push(effect)
		
		function effect1(){
    
    
			app.innerText = state.name
			console.log('副作用函数2被执行')
		}
		effectBucket.push(effect1)
		
		state.name = 'zs'
	</script>
</body>

Insira a descrição da imagem aqui

Mas e se passarmos duas funções de efeitos colaterais idênticas?

function effect(){
    
    
	app.innerText = state.name
	console.log('副作用函数1被执行')
}
effectBucket.push(effect)
effectBucket.push(effect)

Insira a descrição da imagem aqui
Verificou-se que existem duas funções de efeito duplicadas na lista. Se a lista for longa, foreach também desperdiçará tempo, o que prejudicará muito o desempenho. es6 possui uma estrutura de dados Set que pode nos ajudar a resolver esse problema.

let effectBucket = new Set();

const state = reactive({
    
    name:'xqx'});

function effect(){
    
    
	app.innerText = state.name
	console.log('副作用函数1被执行')
}
effectBucket.add(effect)  //添加两次
effectBucket.add(effect)

function effect1(){
    
    
	app.innerText = state.name
	console.log('副作用函数2被执行')
}
effectBucket.add(effect1)

console.log(effectBucket)

Vamos adicionar o efeito duas vezes para ver como fica o resultado
Insira a descrição da imagem aqui

4.2 Implementar coleta de dependências

Anteriormente processávamos apenas os atributos de um objeto. E se vários atributos precisassem ser alterados? Nossas operações acima farão com que cada função de efeito colateral seja executada.

Suponha que temos uma estrutura como esta

let obj = {
    
    name: 'qx',age:24}

Quando desejo alterar o atributo name, atualizo apenas a função de efeito colateral com o nome, e não todas as funções de efeito colateral da lista. Isso requer coleta de dependências.

4.2.1 Implementação básica

Salve cada função de efeito colateral. Quando a função de efeito colateral for chamada, o método get no proxy será executado. No método get, a função de efeito colateral atual é adicionada à lista, realizando assim a associação entre o atual atributo de dependência e a função de efeito colateral.

As etapas específicas de implementação são as seguintes:

let obj = {
    
    name: 'qx',age:24}
let effectBucket = new Set();

let activeEffect = null;   //1.保存当前的副作用函数状态

function reactive(data) {
    
    
	if(typeof data === Object && data !== null) return 
	return new Proxy(data, {
    
    
		get(target, key) {
    
    
			if(activeEffect != null){
    
                  //4. 将当前保存的副作用函数添加到副作用函数列表中
				effectBucket.add(activeEffect)  
			}
			return target[key];
		},
		set(target, key, value) {
    
    
			target[key] = value
			effectBucket.forEach(fn=>fn())
			return true
		}
	})
}
const state = reactive(obj);
function effectName(){
    
    
	console.log('副作用函数1被执行',state.name)
}
activeEffect = effectName()  // 2.将当前副作用函数赋值给activeEffect 
effectName()                 // 3.调用副作用函数,相当于访问proxy的get方法
activeEffect = null;         // 5.将副作用函数状态置空,给下一个副作用函数用

function effectAge(){
    
    
 	console.log('副作用函数2被执行',state.age)
}
activeEffect = effectAge()
effectAge()
activeEffect = null;

state.name = 'zs'

Insira a descrição da imagem aqui
Simplifique novamente e encapsule o código repetido acima. Ao chamar, ajuste diretamente o método encapsulado

function registEffect(fn) {
    
    
	if (typeof fn !== 'function') return;
	activeEffect = fn();
	fn();
	activeEffect = null;
}

4.3 Melhorar a estrutura do balde

A estrutura Set é como um array, exceto que pode remover duplicatas e não pode implementar atributos diferentes correspondentes a conjuntos diferentes. Precisamos melhorá-lo para que um atributo corresponda a múltiplas coleções.

Outra estrutura de dados, Map, aparece na sua frente. É uma coleção de pares chave-valor, mas o escopo das "chaves" não se limita a strings. Vários tipos de valores (incluindo objetos) podem ser usados ​​como chaves .

Crie uma estrutura como esta.

let a = {
    
    
	name: Set(fn,fn),
	age:Set(fn,fn)
}

Insira a descrição da imagem aqui

let effectBucket = new Map();  //{name:Set(fn,fn),age:Set(fn,fn)}
let activeEffect = null;

function reactive(data) {
    
    
	if (typeof data === Object && data !== null) return
	return new Proxy(data, {
    
    
		get(target, key) {
    
    
			if (activeEffect !== null) {
    
    
				let deptSet;
				if(!effectBucket.get(key)){
    
                           //没有得到key,说明没有添加过
					deptSet = new Set();            //重新创建一个集合
					effectBucket.set(key,deptSet);  //每次添加一个属性{name:Set(fn,fn)}结构
				}
				deptSet.add(activeEffect)
			}
			return target[key];
		},
		set(target, key, value) {
    
    
			target[key] = value
			//从副作用桶中依次取出每一个副作用函数执行
			let deptSet = effectBucket.get(key);
			if(deptSet){
    
                       
				deptSet.forEach(fn => fn())
			}
			return true
		}
	})
}

Continue a encapsular e coletar dependências
para obter

function track(target, key) {
    
    
	if (!activeEffect) return
	let deptSet;
	if (!effectBucket.get(key)) {
    
     //没有得到key,说明没有添加过
		deptSet = new Set(); //重新创建一个集合
		effectBucket.set(key, deptSet);
	}
	deptSet.add(activeEffect)
}

definir

function trigger(target, key) {
    
    
	let deptSet = effectBucket.get(key);
	if (deptSet) {
    
    
		deptSet.forEach((fn) => fn())
	}
}

Insira a descrição da imagem aqui

function track(target, key) {
    
    
	if (!activeEffect) return
	let deptMap =effectBucket.get(key);
	if (!deptMap) {
    
     //没有得到key,说明没有添加过
		deptMap = new Map(); //重新创建一个集合
		effectBucket.set(target, deptMap);
	}
	let depSet = deptMap.get(key)
	if(!depSet){
    
    
		depSet = new Set()
		deptMap.set(key,depSet)
	}
	deptSet.add(activeEffect)
}

function trigger(target, key) {
    
    
	let depMap = effectBucket.get(target)
	if(!depMap) return
	let deptSet = effectBucket.get(key);
	if (deptSet) {
    
    
		deptSet.forEach((fn) => fn())
	}
}

5. Perguntas relacionadas à entrevista

1.Qual é a diferença entre Object.defineProperty e Proxy?

  1. O proxy pode monitorar objetos diretamente em vez de propriedades;
  2. O proxy pode monitorar diretamente as alterações no array;
  3. O proxy possui até 13 métodos de interceptação, não limitados a apply, ownKeys, deleteProperty, has, etc., que Object.defineProperty não possui;
  4. Proxy retorna um novo objeto, só podemos operar o novo objeto para atingir o objetivo, enquanto Object.defineProperty só pode percorrer as propriedades do objeto e modificá-las diretamente.
  5. Como um novo padrão, o Proxy estará sujeito à otimização contínua de desempenho pelos fabricantes de navegadores, que é o lendário bônus de desempenho do novo padrão.
  6. Object.defineProperty tem boa compatibilidade e suporta IE9. No entanto, o Proxy tem problemas de compatibilidade do navegador e não pode ser suavizado com polyfill. Portanto, o autor do Vue afirmou que precisa esperar até a próxima versão principal (3.0) antes de reescrever com Proxy.

2. Qual é a diferença entre vue2.0 e vue3.0? Atualização de ligação bidirecional?

A ligação de dados bidirecional do vue2 é implementada usando uma API do ES5, Object.defineProperty() para sequestrar dados e combiná-los com o modo publicar-assinar.

vue3 usa ProxyAPI do ES6 para fazer proxy de dados. Cada objeto é encapsulado com uma camada de Proxy por meio da função reactive() e as alterações nos atributos são monitoradas por meio do Proxy para monitorar os dados.

Comparado com a versão vue2, aqui estão as vantagens de usar proxy:

  1. defineProperty só pode monitorar uma determinada propriedade e não pode monitorar o objeto inteiro. Você pode omitir entrada, fechamento, etc. para melhorar a eficiência (basta vincular o objeto inteiro diretamente).

  2. Para monitorar um array, você não precisa mais realizar operações específicas no array separadamente. O proxy pode interceptar diretamente operações em todos os dados do tipo de objeto, suportando perfeitamente o monitoramento de arrays.

Obtenha adereços

vue2 pode obter adereços diretamente no bloco de código do script e vue3 os passa pelas instruções de configuração.

API é diferente

Vue2 usa a API de opções (API de opções) e Vue3 usa a API de composição (API de composição).

Criar dados de dados

vue2 coloca dados em dados, vue3 precisa usar um novo método setup(), que é acionado quando o componente é inicializado e construído.

diferentes ciclos de vida

visualizar2 ver 3
antes de criar Antes de setup() começar a criar componentes, ele cria dados e métodos.
criada configurar()
antes de montar Função onBeforeMount executada antes do componente ser montado no nó
montado Função onMounted executada após a conclusão da montagem do componente
antes da atualização Função onBeforeUpdate executada antes da atualização do componente
Atualizada Função onUpdated executada após a conclusão da atualização do componente
antesDestruir Função onBeforeUnmount executada antes do componente ser montado no nó
destruído onUnmounted Função executada antes do componente ser desmontado
ativado Função onActivated executada após a conclusão da desinstalação do componente
desativado onDesativado

Em relação à prioridade de v-if e v-for:

vue2 usa v-if e v-for em um elemento ao mesmo tempo v-for será executado primeiro

vue3 v-if sempre terá efeito antes de v-for

algoritmo diff de vue2 e vue3

visualizar2

O algoritmo diff vue2 compara nós virtuais e retorna um objeto patch para armazenar as diferenças entre os dois nós.Finalmente, a mensagem gravada pelo patch é usada para atualizar localmente o Dom.

O algoritmo diff vue2 compara cada vnode e, para alguns elementos que não participam da atualização, a comparação consome um pouco o desempenho.

ver 3

O algoritmo diff vue3 adicionará patchFlags a cada nó virtual durante a inicialização, e patchFlags é o identificador de otimização.

Somente os vnodes cujos patchFlags foram alterados serão comparados para atualizar a visualização. Os elementos que não foram alterados serão marcados estaticamente e reutilizados diretamente durante a renderização.

3.Como o Vue implementa a vinculação de dados bidirecional?

A ligação bidirecional de dados Vue refere-se principalmente a: visualização de atualização de alterações de dados

  • Quando o conteúdo da caixa de entrada muda, os dados em Dados mudam simultaneamente. Essa é a mudança de View => Data.
  • Quando os dados em Dados são alterados, o conteúdo do nó de texto muda de forma síncrona. Ou seja, alterações em Dados => Visualizar

Vue implementa principalmente vinculação de dados bidirecional por meio das quatro etapas a seguir:

  • A primeira etapa: percorrer recursivamente o objeto de dados da observação, incluindo os atributos do objeto de subatributo, e adicionar setters e getters. Nesse caso, atribuir um valor a este objeto acionará o setter e então as alterações nos dados poderão ser monitoradas.

  • Etapa 2: compilar analisa as instruções do modelo, substitui as variáveis ​​no modelo por dados e, em seguida, inicializa a visualização da página de renderização, vincula a função de atualização ao nó correspondente a cada instrução e adiciona um assinante para monitorar os dados. Uma vez que os dados mudam , receba a notificação e atualize a visualização.

  • Etapa 3: Os assinantes do Watcher são a ponte de comunicação entre o Observer e o Compile. As principais coisas que eles fazem são:

    • 1. Adicione-se ao atributo subscriber (dep) ao se instanciar.
    • 2. Deve ter um método update()
    • 3. Quando notificado por dep.notice() sobre alterações de atributos, você pode chamar seu próprio método update() e acionar o retorno de chamada vinculado em Compile, então estará pronto.
  • Etapa 4: como entrada para vinculação de dados, MVVM integra Observer, Compile e Watcher, usa Observer para monitorar alterações em seus próprios dados de modelo, usa Compile para analisar e compilar instruções de modelo e, finalmente, usa Watcher para construir a conexão entre Observer e Compile Ponte de comunicação, alcançando o efeito de ligação bidirecional de alterações de dados -> visualizar atualizações; visualizar alterações interativas (entrada) -> alterações no modelo de dados.

4. Apresente as diferenças entre Set, Map, WeakSet e WeakMap?

Conjunto Conjunto
é uma estrutura de dados chamada conjunto, que é uma combinação de um monte de estruturas de memória não ordenadas, relacionadas e não repetitivas. Define os elementos da loja no formato [valor, valor]

  1. Os membros não podem ser duplicados;
  2. Apenas valores-chave, sem nomes de chaves, um pouco como um array;
  3. Pode ser percorrido, os métodos são add, delete, has, clear

Conjunto Fraco

  1. Os membros do WeackSet só podem ser tipos de referência, e não valores de outros tipos;
  2. Os membros são referências fracas e podem desaparecer a qualquer momento (não contabilizados no mecanismo de coleta de lixo). Ele pode ser usado para salvar nós DOM e tem menos probabilidade de causar vazamentos de memória;
  3. Não pode ser percorrido, não há atributo de tamanho e os métodos incluem add, delete e has;

Mapa
Mapa é uma estrutura de dados chamada dicionário. Cada elemento possui um campo chamado chave, e as chaves dos diferentes elementos são diferentes. Os dicionários são armazenados no formato [chave, valor].

  1. Essencialmente, é uma coleção de pares de valores-chave, semelhante a uma coleção. As chaves do mapa podem ser qualquer tipo de dados, até mesmo funções. ;
  2. Pode ser percorrido, possui vários métodos e pode ser convertido em vários formatos de dados;
  3. As chaves do Mapa estão, na verdade, vinculadas aos endereços de memória.Enquanto os endereços de memória forem diferentes, eles serão considerados como duas chaves;

Mapa Fraco

  1. Aceito apenas 对象como nomes de chaves (exceto nulo), outros tipos de valores não são aceitos como nomes de chaves;
  2. O objeto apontado pelo nome da chave não está incluído no mecanismo de coleta de lixo;
  3. Não pode ser percorrido e não existe um método claro.Os métodos são iguais a get, set, has e delete;

5.Por que o Vue2.0 não consegue verificar alterações em arrays? Como resolver isso?

  • Não foi possível detectar a adição de array/objeto?

O Vue detecta alterações de dados por meio de Object.defineProperty, portanto é compreensível que ele não possa monitorar a operação de adição do array, pois essa operação de detecção e ligação foi feita para todas as propriedades no construtor.

  • Não é possível detectar operações que alteram uma matriz por índice? Isso é vm.items[indexOfItem] = newValue?
function defineReactive(data, key, value) {
    
    
	Object.defineProperty(data, key, {
    
    
		enumerable: true,
		configurable: true,
		get: function defineGet() {
    
    
			console.log(`get key: ${
      
      key} value: ${
      
      value}`)
			return value
		},
		set: function defineSet(newVal) {
    
    
			console.log(`set key: ${
      
      key} value: ${
      
      newVal}`)
			value = newVal
		}
	})
}

function observe(data) {
    
    
	Object.keys(data).forEach(function(key) {
    
    
		console.log(data, key, data[key])
		defineReactive(data, key, data[key])
	})
}

let arr = [1, 2, 3]
observe(arr)

O Object.defineProperty original descobriu que poderia ser atribuído por índice e também acionou o método set, mas por que o Vue não poderia fazer isso?
Insira a descrição da imagem aqui

Para objetos, cada alteração de dados enumerará as propriedades do objeto.Geralmente, o número de propriedades do próprio objeto é limitado, portanto, a perda de desempenho causada pela passagem da enumeração e outros métodos é insignificante, mas e as matrizes? O número de elementos contidos no array pode chegar a dezenas de milhares. Supondo que cada atualização dos elementos do array acione enumeração/travessia, a perda de desempenho não será proporcional à experiência do usuário obtida, portanto o Vue não consegue detectar a mudança do array.

solução

  • variedade
  1. this.$set(array, índice, dados)
//这是个深度的修改,某些情况下可能导致你不希望的结果,因此最好还是慎用
this.dataArr = this.originArr
this.$set(this.dataArr, 0, {
    
    data: '修改第一个元素'})
console.log(this.dataArr)        
console.log(this.originArr)  //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果 
  1. emendar
//因为splice会被监听有响应式,而splice又可以做到增删改。
  1. Use variáveis ​​temporárias para transferência
let tempArr = [...this.targetArr]
tempArr[0] = {
    
    data: 'test'}
this.targetArr = tempArr
  • objeto
  1. this.$set(obj, key,value) - pode adicionar e alterar
  2. Adicione deep: true ao observar o monitoramento profundo. Você só pode monitorar alterações nos valores dos atributos. Atributos novos e excluídos não podem ser monitorados.
this.$watch('blog', this.getCatalog, {
    
    
    deep: true
    // immediate: true // 是否第一次触发
  });
  1. Monitore diretamente uma chave durante a observação
watch: {
    
    
  'obj.name'(curVal, oldVal) {
    
    
    // TODO
  }
}

Acho que você gosta

Origin blog.csdn.net/2201_75499330/article/details/132423460
Recomendado
Clasificación