Dados responsivos Vue3
- Prefácio
- 1. O que é procuração?
- 2. Implemente a função reativa mais básica
- 3. Implemente um sistema responsivo básico
- 4. Melhore o sistema responsivo básico
- 5. Perguntas relacionadas à entrevista
-
- 1.Qual é a diferença entre Object.defineProperty e Proxy?
- 2. Qual é a diferença entre vue2.0 e vue3.0? Atualização de ligação bidirecional?
- 3.Como o Vue implementa a vinculação de dados bidirecional?
- 4. Apresente as diferenças entre Set, Map, WeakSet e WeakMap?
- 5.Por que o Vue2.0 não consegue verificar alterações em arrays? Como resolver isso?
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:
-
- 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).
-
- 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)
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))
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>
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>
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)
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
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'
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)
}
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())
}
}
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?
- O proxy pode monitorar objetos diretamente em vez de propriedades;
- O proxy pode monitorar diretamente as alterações no array;
- 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;
- 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.
- 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.
- 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:
-
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).
-
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]
- Os membros não podem ser duplicados;
- Apenas valores-chave, sem nomes de chaves, um pouco como um array;
- Pode ser percorrido, os métodos são add, delete, has, clear
Conjunto Fraco
- Os membros do WeackSet só podem ser tipos de referência, e não valores de outros tipos;
- 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;
- 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].
- 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. ;
- Pode ser percorrido, possui vários métodos e pode ser convertido em vários formatos de dados;
- 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
- Aceito apenas
对象
como nomes de chaves (exceto nulo), outros tipos de valores não são aceitos como nomes de chaves; - O objeto apontado pelo nome da chave não está incluído no mecanismo de coleta de lixo;
- 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?
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
- this.$set(array, índice, dados)
//这是个深度的修改,某些情况下可能导致你不希望的结果,因此最好还是慎用
this.dataArr = this.originArr
this.$set(this.dataArr, 0, {
data: '修改第一个元素'})
console.log(this.dataArr)
console.log(this.originArr) //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果
- emendar
//因为splice会被监听有响应式,而splice又可以做到增删改。
- Use variáveis temporárias para transferência
let tempArr = [...this.targetArr]
tempArr[0] = {
data: 'test'}
this.targetArr = tempArr
- objeto
- this.$set(obj, key,value) - pode adicionar e alterar
- 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 // 是否第一次触发
});
- Monitore diretamente uma chave durante a observação
watch: {
'obj.name'(curVal, oldVal) {
// TODO
}
}