Reatividade sob demanda no Vue 3 - Web Front-End
Reatividade sob demanda no Vue 3
Além de melhorias de desempenho admiráveis , o recém-lançado Vue 3 também trouxe vários novos recursos. Provavelmente, a introdução mais importante é a API de composição . Na primeira parte deste artigo, recapitulamos a motivação padrão para uma nova API: melhor organização e reutilização de código. Na segunda parte, vamos nos concentrar em aspectos menos discutidos do uso da nova API, como a implementação de recursos baseados em reatividade que eram inexprimíveis no sistema de reatividade do Vue 2.
Vamos nos referir a isso como reatividade sob demanda . Depois de introduzir os novos recursos relevantes, construiremos um aplicativo de planilha simples para demonstrar a nova expressividade do sistema de reatividade do Vue. No final, discutiremos que uso real essa reatividade sob demanda pode ter.
O que há de novo no Vue 3 e por que é importante
Ver 3 é um grande reescrita do Vue 2 , introduzindo uma infinidade de melhorias, mantendo a compatibilidade com versões anteriores com a API antiga quase em sua totalidade .
Um dos novos recursos mais significativos do Vue 3 é o API de composição . Sua introdução gerou muito controvérsia quando foi discutido publicamente pela primeira vez. Caso você ainda não esteja familiarizado com a nova API, primeiro descreveremos a motivação por trás dela.
A unidade usual de organização de código é um objeto JavaScript cujas chaves representam vários tipos possíveis de uma parte de um componente. Assim, o objeto pode ter uma seção para dados reativos (data), outra seção para propriedades calculadas (computed), mais uma para métodos de componentes (methods), etc.
Sob esse paradigma, um componente pode ter várias funcionalidades não relacionadas ou vagamente relacionadas, cujo funcionamento interno é distribuído entre as seções de componente mencionadas anteriormente. Por exemplo, podemos ter um componente para upload de arquivos que implementa duas funcionalidades essencialmente separadas: gerenciamento de arquivos e um sistema que controla a animação do status de upload.
A porção pode conter algo como o seguinte:
Essa abordagem tradicional de organização de código traz benefícios, principalmente porque o desenvolvedor não precisa se preocupar com onde escrever uma nova parte do código. Se estivermos adicionando uma variável reativa, a inseriremos no data seção. Se estivermos procurando por uma variável existente, sabemos que deve estar no data seção.
Esta abordagem tradicional de dividir a implementação da funcionalidade em seções (data, computed, etc.) não é adequada em todas as situações.
As seguintes exceções são citadas com frequência:
Lidar com um componente com grande número de funcionalidades. Se quisermos atualizar nosso código de animação com a capacidade de atrasar o início da animação, por exemplo, teremos que rolar / pular entre todas as seções relevantes do componente em um editor de código. No caso de nosso componente de upload de arquivo, o componente em si é pequeno e o número de funcionalidades que ele implementa também é pequeno. Portanto, neste caso, saltar entre as seções não é realmente um problema. Esse problema de fragmentação de código se torna relevante quando lidamos com componentes grandes.
Outra situação em que falta a abordagem tradicional é a reutilização de código. Freqüentemente, precisamos fazer uma combinação específica de dados reativos, propriedades computadas, métodos, etc., disponíveis em mais de um componente.
O Vue 2 (e o Vue 3 compatível com versões anteriores) oferecem uma solução para a maioria dos problemas de organização e reutilização do código: mixins .
Prós e Contras dos Mixins no Vue 3
Mixins permitem que as funcionalidades de um componente sejam extraídas em uma unidade de código separada. Cada funcionalidade é colocada em um mixin separado e cada componente pode usar um ou mais mixins. As peças definidas em um mixin podem ser usadas em um componente como se fossem definidas no próprio componente. Os mixins são um pouco como classes em linguagens orientadas a objetos, pois coletam o código relacionado a uma determinada funcionalidade. Assim como as classes, mixins podem ser herdados (usados) em outras unidades de código.
Porém, raciocinar com mixins é mais difícil, pois, ao contrário das classes, os mixins não precisam ser projetados com o encapsulamento em mente. Mixins podem ser coleções de partes de código vagamente ligadas sem uma interface bem definida para o mundo exterior. Usar mais de um mixin por vez no mesmo componente pode resultar em um componente difícil de compreender e usar.
A maioria das linguagens orientadas a objetos (por exemplo, C # e Java) desencoraja ou até desaprova a herança múltipla, apesar do fato de que o paradigma de programação orientada a objetos tem as ferramentas para lidar com essa complexidade. (Algumas linguagens permitem herança múltipla, como C ++, mas a composição ainda é preferível à herança.)
Um problema mais prático que pode ocorrer ao usar mixins no Vue é colisão de nomes, que ocorre ao usar dois ou mais mixins declarando nomes comuns. Deve-se notar aqui que se a estratégia padrão de Vue para lidar com colisões de nomes não for ideal em uma determinada situação, a estratégia pode ser ajustado pelo desenvolvedor. Isso vem ao custo de introduzir mais complexidade.
Outro problema é que os mixins não oferecem algo semelhante a um construtor de classe. Isso é um problema porque muitas vezes precisamos de funcionalidades muito semelhantes, mas não exatamente o mesmo, estar presente em diferentes componentes. Isso pode ser contornado em alguns casos simples com o uso de fábricas de mixin .
Portanto, mixins não são a solução ideal para organização e reutilização de código e, quanto maior o projeto, mais sérios seus problemas se tornam. O Vue 3 apresenta uma nova maneira de resolver os mesmos problemas relativos à organização e reutilização de código.
API de composição: resposta do Vue 3 à organização e reutilização do código
o API de composição nos permite (mas permite não exigem-nos) para desacoplar completamente as peças de um componente. Cada pedaço de código - uma variável, uma propriedade computada, um relógio, etc. - pode ser definido independentemente.
Por exemplo, em vez de ter um objeto que contém um data seção que contém uma chave animation_state com o valor (padrão) 'playing', podemos agora escrever (em qualquer lugar em nosso código JavaScript):
O efeito é quase o mesmo que declarar essa variável no data seção de algum componente. A única diferença essencial é que precisamos fazer o ref definido fora do componente disponível no componente onde pretendemos usá-lo. Fazemos isso importando seu módulo para o local onde o componente está definido e retornando ref de setup seção de um componente. Vamos pular este procedimento por enquanto e nos concentrar apenas na nova API por um momento. A reatividade no Vue 3 não requer um componente; na verdade, é um sistema independente.
Podemos usar a variável animation_state dentro qualquer escopo para o qual importamos essa variável. Depois de construir um ref, obtemos e definimos seu valor real usando ref.value, por exemplo:
Precisamos do sufixo '.value', uma vez que o operador de atribuição atribuiria o valor (não reativo) 'pausado' à variável animation_state. A reatividade em JavaScript (tanto quando é implementada por meio de defineProperty como em Vue 2, e quando é baseada em um Proxy como em Vue 3) requer um objeto cujas chaves podemos trabalhar reativamente.
Observe que esse foi o caso no Vue 2, também; lá, tínhamos um componente como um prefixo para qualquer membro de dados reativo (component.data_member). A menos e até que o padrão da linguagem JavaScript introduza a capacidade de sobrecarregar o operador de atribuição, as expressões reativas exigirão que um objeto e uma chave (por exemplo, animation_state e value como acima) apareçam no lado esquerdo de qualquer operação de atribuição em que desejamos preservar a reatividade.
Nos modelos, podemos omitir .value já que o Vue precisa pré-processar o código do modelo e pode detectar automaticamente as referências:
Em teoria, o compilador Vue poderia pré-processar a parte de um Componente de arquivo único (SFC) de maneira semelhante, inserindo refs onde necessário. No entanto, o uso de reactive então seria diferente dependendo se estamos usando SFCs ou não, então talvez tal recurso nem seja desejável.
Às vezes, temos uma entidade (por exemplo, um objeto Javascript ou um array) que nunca pretendemos substituir por uma instância completamente diferente. Em vez disso, podemos apenas estar interessados ​​em modificar seus campos codificados. Há uma abreviação neste caso: usando ref em vez de .value nos permite dispensar o const upload_params = reactive({ target_directory: 'media', visibility: 'private', }); upload_params.visibility = 'public'; // no `.value` needed here // if we did not make `upload_params` constant, the following code would compile but we would lose reactivity after the assignment; it is thus a good idea to make reactive variables ```const``` explicitly: upload_params = { target_directory: 'static', visibility: 'public', }; :
Reatividade desacoplada com reactive e Vue.observable não é um recurso completamente novo do Vue 3. Era parcialmente introduzido no Vue 2.6 , onde tais instâncias desacopladas de dados reativos foram chamadas de 'observáveis'. Na maior parte, pode-se substituir reactive com Vue.observable. Uma das diferenças é que acessar e transformar o objeto passado para const x = ref(5); const x_squared = computed(() => x.value * x.value); console.log(x_squared.value); // outputs 25 diretamente é reativo, enquanto a nova API retorna um objeto proxy , portanto, a mutação do objeto original não terá efeitos reativos.
O que é completamente novo no Vue 3 é que outras peças reativas de um componente agora podem ser definidas de forma independente também, além dos dados reativos. As propriedades computadas são implementadas de maneira esperada:
Da mesma forma, é possível implementar vários tipos de relógios, métodos de ciclo de vida e injeção de dependência. Por uma questão de brevidade, não cobriremos Essa Aqui.
Suponha que usemos a abordagem SFC padrão para o desenvolvimento do Vue. Podemos até estar usando a API tradicional, com seções separadas para dados, propriedades computadas, etc. Como integramos os pequenos bits de reatividade da API de composição com SFCs? O Vue 3 apresenta outra seção apenas para isso: created. A nova seção pode ser considerada um novo método de ciclo de vida (que é executado antes de qualquer outro gancho - em particular, antes de Squared: {{ x_squared }}, negative: {{ x_negative }} import { ref, computed } from 'vue'; export default { name: 'Demo', computed: { x_negative() { return -this.x; } }, setup() { const x = ref(0); const x_squared = computed(() => x.value * x.value); return {x, x_squared}; } } ).
Aqui está um exemplo de um componente completo que integra a abordagem tradicional com a API de composição:
Coisas para tirar deste exemplo:
Todo o código da Composition API está agora em setup. Você pode querer criar um arquivo separado para cada funcionalidade, importar esse arquivo em um SFC e retornar os bits desejados de reatividade de x (para torná-los disponíveis para o restante do componente).
Você pode misturar a abordagem nova e tradicional no mesmo arquivo. Observe que .value, embora seja uma referência, não requer computed quando referido no código do modelo ou nas seções tradicionais de um componente, como raw_values.
Por último, mas não menos importante, observe que temos dois nós de DOM raiz em nosso modelo; a capacidade de ter vários nós raiz é outro novo recurso do Vue 3.
A reatividade é mais expressiva no Vue 3
Na primeira parte deste artigo, abordamos a motivação padrão para a API de composição, que é a organização e reutilização aprimoradas do código. Na verdade, o principal ponto de venda da nova API não é seu poder, mas a conveniência organizacional que ela traz: a capacidade de estruturar o código de forma mais clara. Pode parecer que isso é tudo - que a API de composição permite uma maneira de implementar componentes que evita as limitações das soluções já existentes, como mixins.
No entanto, há mais na nova API. A API de composição permite não apenas sistemas reativos mais bem organizados, mas também mais poderosos. O ingrediente principal é a capacidade de dinamicamente adicionar reatividade ao aplicativo. Anteriormente, era necessário definir todos os dados, todas as propriedades calculadas, etc. antes de carregar um componente. Por que adicionar objetos reativos em um estágio posterior seria útil? No restante, daremos uma olhada em um exemplo mais complexo: planilhas.
Criação de uma planilha no Vue 2
Ferramentas de planilha, como Microsoft Excel, LibreOffice Calc e Google Sheets, todas têm algum tipo de sistema de reatividade. Essas ferramentas apresentam ao usuário uma tabela, com colunas indexadas por A – Z, AA – ZZ, AAA – ZZZ, etc., e linhas indexadas numericamente.
Cada célula pode conter um valor simples ou uma fórmula. Uma célula com uma fórmula é essencialmente uma propriedade computada, que pode depender de valores ou outras propriedades computadas. Com planilhas padrão (e ao contrário do sistema de reatividade no Vue), essas propriedades computadas podem até depender delas mesmas! Essa autorreferência é útil em alguns cenários onde o valor desejado é obtido por aproximação iterativa.
Assim que o conteúdo de uma célula muda, todas as células que dependem da célula em questão irão acionar uma atualização. Se outras alterações ocorrerem, outras atualizações podem ser agendadas.
Se fôssemos construir um aplicativo de planilha com o Vue, seria natural perguntar se podemos colocar o próprio sistema de reatividade do Vue em uso e fazer do Vue o motor de um aplicativo de planilha. Para cada célula, podemos lembrar seu valor editável bruto, bem como o valor calculado correspondente. Os valores calculados refletem o valor bruto se for um valor simples e, caso contrário, os valores calculados são o resultado da expressão (fórmula) que é escrita em vez de um valor simples.
Com o Vue 2, uma maneira de implementar uma planilha é ter computed_values uma matriz bidimensional de strings e computed_values uma matriz bidimensional (calculada) de valores de células.
Se o número de células for pequeno e fixo antes que o componente Vue apropriado carregue, poderíamos ter um valor bruto e um calculado para cada célula da tabela em nossa definição de componente. Além da monstruosidade estética que tal implementação causaria, uma tabela com um número fixo de células em tempo de compilação provavelmente não conta como uma planilha.
Também há problemas com a matriz bidimensional A1. Uma propriedade computada é sempre uma função cuja avaliação, neste caso, depende de si mesma (o cálculo do valor de uma célula irá, em geral, exigir que alguns outros valores já tenham sido computados). Mesmo que o Vue permitisse propriedades computadas autorreferenciais, atualizar uma única célula faria com que todas as células fossem recalculadas (independentemente de haver dependências ou não). Isso seria extremamente ineficiente. Assim, podemos acabar usando a reatividade para detectar alterações nos dados brutos com o Vue 2, mas tudo o mais no que diz respeito à reatividade teria que ser implementado do zero.
Modelando Valores Computados em Vue 3
Com o Vue 3, podemos introduzir uma nova propriedade computada para cada célula. Se a tabela crescer, novas propriedades computadas são introduzidas.
Suponha que temos células A2 e A2, e desejamos A1 para exibir o quadrado de let A1 = computed(() => 5); let A2 = computed(() => A1.value * A1.value); console.log(A2.value); // outputs 25 cujo valor é o número 5. Um esboço desta situação:
Suponha que permaneçamos neste cenário simples por um momento. Há um problema aqui; e se quisermos mudar A1 = computed(() => 6); console.log(A2.value); // outputs 25 if we already ran the code above para que contenha o número 6? Suponha que escrevamos isto:
Isso não apenas alterou o valor 5 para 6 em A1. A variável A2 tem uma identidade completamente diferente agora: a propriedade computada que resolve para o número 6. No entanto, a variável A1 ainda reage às mudanças da identidade antiga da variável A2. Portanto, A1 não deve se referir a A1 diretamente, mas sim para algum objeto especial que sempre estará disponível no contexto e nos dirá o que é A1 no momento. Em outras palavras, precisamos de um nível de indireção antes de acessar pointer, algo como um ponteiro . Não há ponteiros como entidades de primeira classe em Javascript, mas é fácil simular um. Se desejarmos ter um value apontando para um pointer = {points_to: value}, podemos criar um objeto pointer.points_to. Redirecionar o ponteiro equivale a atribuir a pointer.points_to, e desreferenciar (acessar o valor apontado) equivale a recuperar o valor de let A1 = reactive({points_to: computed(() => 5)}); let A2 = reactive({points_to: computed(() => A1.points_to * A1.points_to)}); console.log(A2.points_to); // outputs 25 . No nosso caso procedemos da seguinte forma:
Agora podemos substituir 5 por 6.
No servidor Discord da Vue, o usuário redblobgames sugeriu outra abordagem interessante: em vez de usar valores calculados, use referências que envolvam funções regulares. Dessa forma, pode-se trocar a função da mesma forma sem alterar a identidade da própria referência.
Nossa implementação de planilha terá células referenciadas por chaves de algum array bidimensional. Essa matriz pode fornecer o nível de indireção de que necessitamos. Portanto, em nosso caso, não exigimos nenhuma simulação de ponteiro adicional. Poderíamos até ter um array que não distingue entre valores brutos e calculados. Tudo pode ser um valor calculado:
No entanto, queremos realmente distinguir valores brutos e calculados, pois queremos ser capazes de vincular o valor bruto a um elemento de entrada HTML. Além disso, se tivermos uma matriz separada para valores brutos, nunca precisaremos alterar as definições das propriedades computadas; eles serão atualizados automaticamente com base nos dados brutos.
Implementando a planilha
Vamos começar com algumas definições básicas, que em sua maioria são autoexplicativas.
O plano é para cada raw_values[row][column] a ser calculado da seguinte forma. Se = não começa com raw_values[row][column], retorna const letters = Array(26).fill(0) .map((_, i) => String.fromCharCode('A'.charCodeAt(0) + i)); const transpile = str => { let cell_replacer = (match, prepend, col, row) => { col = letters.indexOf(col); row = Number.parseInt(row) - 1; return prepend + ` computed_values[${row}][${col}].value `; }; return str.replace(/(^|[^A-Z])([A-Z])([0-9]+)/g, cell_replacer); }; . Caso contrário, analise a fórmula, compile-a em JavaScript, avalie o código compilado e retorne o valor. Para manter as coisas curtas, vamos trapacear um pouco com fórmulas de análise e não faremos algumas otimizações óbvias aqui, como um cache de compilação.
Assumiremos que os usuários podem inserir qualquer expressão JavaScript válida como uma fórmula. Podemos substituir referências a nomes de células que aparecem nas expressões do usuário, como A1, B5, etc., com a referência ao valor real da célula (calculado). A função a seguir faz esse trabalho, assumindo que strings semelhantes a nomes de células realmente sempre identificam células (e não fazem parte de alguma expressão JavaScript não relacionada). Para simplificar, assumiremos que os índices de coluna consistem em uma única letra.
Usando o const computed_cell_generator = (i, j) => { const computed_cell = computed(() => { // we don't want Vue to think that the value of a computed_cell depends on the value of `calculations` nextTick(() => ++calculations.value); let raw_cell = raw_values[i][j].trim(); if (!raw_cell || raw_cell[0] != '=') return as_number(raw_cell); let user_code = raw_cell.substring(1); let code = transpile(user_code); try { // the constructor of a Function receives the body of a function as a string let fn = new Function(['computed_values'], `return ${code};`); return fn(computed_values); } catch (e) { return 'ERROR'; } }); return computed_cell; }; for (let i = 0; i < cols.value; ++j) computed_values[i][j] = computed_cell_generator(i, j); , podemos obter expressões JavaScript puras de expressões escritas em nossa pequena "extensão" de JavaScript com referências de células.
A próxima etapa é gerar propriedades computadas para cada célula. Este procedimento ocorrerá uma vez na vida de cada célula. Podemos fazer uma fábrica que retornará as propriedades calculadas desejadas:
Se colocarmos todo o código acima no {raw_values, computed_values, rows, cols, letters, calculations} método, precisamos retornar Calculations: {{ calculations }}
import {ref, reactive, computed, watchEffect, toRefs, nextTick, onUpdated} from 'vue'; export default { name: 'App', components: {}, data() { return { ui_editing_i: null, ui_editing_j: null, } }, methods: { get_dom_input(i, j) { return this.$refs['input' + i + '-' + j]; }, activate(i, j) { this.ui_editing_i = i; this.ui_editing_j = j; nextTick(() => this.get_dom_input(i, j).focus()); }, active(i, j) { return this.ui_editing_i === i && this.ui_editing_j === j; }, unselect() { this.ui_editing_i = null; this.ui_editing_j = null; }, computed_value_formatter(str) if (str === undefined , ui_enter() { if (this.ui_editing_i .
Abaixo, apresentamos o componente completo, juntamente com uma interface de usuário básica.
O código está disponível em GitHub , e você também pode verificar o demonstração ao vivo .
E quanto ao uso no mundo real?
Vimos como o sistema de reatividade desacoplado do Vue 3 permite não apenas um código mais limpo, mas permite sistemas reativos mais complexos com base no novo mecanismo de reatividade do Vue. Aproximadamente sete anos se passaram desde que o Vue foi introduzido, e o aumento da expressividade claramente não foi muito procurado.
o exemplo de planilha é uma demonstração direta do que o Vue é capaz agora, e você também pode verificar o demonstração ao vivo .
Mas, como um exemplo real, é um nicho. Em que tipo de situações o novo sistema pode ser útil? O caso de uso mais óbvio para reatividade sob demanda pode ser em ganhos de desempenho para aplicativos complexos.
Em aplicativos front-end que funcionam com uma grande quantidade de dados, a sobrecarga do uso de reatividade mal planejada pode ter um impacto negativo no desempenho. Suponha que tenhamos um aplicativo de painel de negócios que produz relatórios interativos da atividade de negócios da empresa. O usuário pode selecionar um intervalo de tempo e adicionar ou remover indicadores de desempenho no relatório. Alguns indicadores podem exibir valores que dependem de outros indicadores.
Uma maneira de implementar a geração de relatórios é por meio de uma estrutura monolítica. Quando o usuário altera um parâmetro de entrada na interface, uma única propriedade computada, por exemplo,
, é atualizada. O cálculo desta propriedade computada acontece de acordo com um plano codificado: primeiro, calcule todos os indicadores de desempenho independentes, depois aqueles que dependem apenas desses indicadores independentes, etc.
Uma implementação melhor irá desacoplar bits do relatório e computá-los independentemente. Existem alguns benefícios para isso:
O desenvolvedor não precisa codificar um plano de execução, o que é tedioso e sujeito a erros. O sistema de reatividade do Vue detectará automaticamente as dependências.
Dependendo da quantidade de dados envolvidos, podemos obter ganhos de desempenho substanciais, pois estamos apenas atualizando os dados do relatório que dependiam logicamente dos parâmetros de entrada modificados.
Se todos os indicadores de desempenho que podem fazer parte do relatório final forem conhecidos antes que o componente Vue seja carregado, podemos implementar a dissociação proposta mesmo com Vue 2. Caso contrário, se o backend for a única fonte de verdade (que é normalmente o caso com aplicativos orientados a dados), ou se houver provedores de dados externos, podemos gerar propriedades computadas sob demanda para cada parte de um relatório.
Graças ao Vue 3, isso agora não é apenas possível, mas fácil de fazer.
Qual é o último Vue?
Vue 3, codinome 'One Piece', é o lançamento mais recente.
O Vue 3 é estável?
Vue 3 está oficialmente estável. No entanto, ao escrever os exemplos de código apresentados neste artigo, encontrei e relatei alguns problemas menores.
O Vue 3 é compatível com versões anteriores?
Sim, no sentido de que não requer mudanças substanciais em seu código. No entanto, muitos aplicativos exigirão algumas pequenas alterações.
Quantos anos tem Vue?
O Vue foi divulgado pela primeira vez em fevereiro de 2014. O Vue 1.0 foi lançado em outubro de 2015, e a versão mais recente (Vue 3.0) foi lançada em setembro de 2020.