9 dicas de desempenho do Vue

Este artigo tem nove dicas sobre como melhorar o desempenho do seu aplicativo Vue, aumentar a velocidade de exibição e reduzir o tamanho do pacote.

Trabalhar com componentes


Componentes funcionais


Suponha que tenhamos um componente pequeno e simples. Tudo o que ele faz é exibir uma tag específica, dependendo do valor passado:

<template> <div> <div v-if="value"></div> <section v-else></section> </div> </template> <script> export default { props: ['value'] } </script> 

Este componente pode ser otimizado adicionando o atributo funcional . Um componente funcional é compilado em uma função simples e não possui um estado local. Devido a isso, seu desempenho é muito maior:

 <template functional> <div> <div v-if="props.value"></div> <section v-else></section> </div> </template> <script> export default { props: ['value'] } </script> 

Um exemplo de como os componentes funcionais podem parecer no Vue v3.0

Separação em componentes filhos


Imagine um componente para exibir, que você precisa executar em uma tarefa complexa:

 <template> <div :style="{ opacity: number / 300 }"> <div>{{ heavy() }}</div> </div> </template> <script> export default { props: ['number'], methods: { heavy () { /*   */ } } } </script> 

O problema aqui é que o Vue executará o método heavy () toda vez que o componente for renderizado novamente, ou seja, toda vez que o valor dos props for alterado.

Podemos otimizar facilmente esse componente se separarmos o método pesado em um componente filho:

 <template> <div :style="{ opacity: number / 300 }"> <ChildComp/> </div> </template> <script> export default { props: ['number'], components: { ChildComp: { methods: { heavy () { /*   */ } }, render (h) { return h('div', this.heavy()) } } } } </script> 

Porque O Vue ignora automaticamente a renderização de componentes nos quais os dados dependentes não foram alterados. Assim, quando os props são alterados no componente pai, o filho será reutilizado e o método heavy () não será reiniciado.

Observe que isso só faz sentido se o componente filho não tiver dependência de dados no componente pai. Caso contrário, o filho será recriado junto com o pai, e essa otimização não fará sentido.

Cache local do Getter


O componente a seguir possui algum tipo de propriedade calculada com base na segunda propriedade calculada:

 <template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> export default { props: ['start'], computed: { base () { return 42 }, result () { let result = this.start for (let i = 0; i < 1000; i++) { result += this.base } return result } } } </script> 

O importante aqui é que a propriedade base é chamada em um loop, o que leva a complicações. Sempre que acessa dados reativos, o Vue executa alguma lógica para determinar como e quais dados você está acessando para criar dependências e assim por diante. Essas pequenas despesas gerais são resumidas se houver muitas chamadas, como no nosso exemplo.

Para corrigir isso, basta acessar a base uma vez e salvar o valor em uma variável local:

 <template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> export default { props: ['start'], computed: { base () { return 42 }, result () { const base = this.base // <-- let result = this.start for (let i = 0; i < 1000; i++) { result += base } return result } } } </script> 

Reutilizando o DOM com v-show


Veja o seguinte exemplo:

 <template functional> <div> <div v-if="props.value"> <Heavy :n="10000"/> </div> <section v-else> <Heavy :n="10000"/> </section> </div> </template> 

Aqui temos um componente wrapper que usa v-if e v-else para alternar alguns componentes filhos.

É importante entender como o v-if funciona. Cada vez que um estado é alternado, um componente filho será completamente destruído (o gancho destroyed () é chamado, todos os nós serão excluídos do DOM) e o segundo será completamente criado e montado novamente. E se esses componentes forem "pesados", você terá uma interface travada no momento da troca.

Solução mais produtiva - use v-show

 <template functional> <div> <div v-show="props.value"> <Heavy :n="10000"/> </div> <section v-show="!props.value"> <Heavy :n="10000"/> </section> </div> </template> 

Nesse caso, os dois componentes filhos serão criados e montados imediatamente e existirão simultaneamente. Portanto, o Vue não precisa destruir e criar componentes ao alternar. Tudo o que ele faz é ocultar um e mostrar o segundo com CSS. Portanto, mudar o estado será muito mais rápido, mas você deve entender que isso levará a altos custos de memória.

Use <keep-alive>


Portanto, um componente simples é um componente de invólucro em um roteador .

 <template> <div id="app"> <router-view/> </div> </template> 

O problema é semelhante ao exemplo anterior - todos os componentes do seu roteador serão criados, montados e destruídos durante as transições entre rotas.

E a solução aqui é semelhante - dizer ao Vue para não destruir, mas armazenar em cache e reutilizar componentes. Você pode fazer isso usando o componente interno <keep-alive> interno:

 <template> <div id="app"> <keep-alive> <router-view/> </keep-alive> </div> </template> 

Essa otimização levará ao aumento do consumo de memória, pois o Vue suportará mais componentes "vivos". Portanto, você não deve usar essa abordagem sem pensar em todas as rotas, especialmente se tiver muitas delas. Você pode usar os atributos de inclusão e exclusão para definir as regras de quais rotas devem ser armazenadas em cache e quais não:

 <template> <div id="app"> <keep-alive include="home,some-popular-page"> <router-view/> </keep-alive> </div> </template> 

Renderização adiada


O exemplo a seguir é um componente que possui vários componentes filhos muito pesados:

 <template> <div> <h3>I'm an heavy page</h3> <Heavy v-for="n in 10" :key="n"/> <Heavy class="super-heavy" :n="9999999"/> </div> </template> 

O problema é que o processamento de componentes é uma operação síncrona no encadeamento principal. E neste exemplo, o navegador não exibirá nada até que o processamento de todos os componentes, pai e filho, seja concluído. E se levar muito tempo para processar subsidiárias, você terá atrasos na interface ou em uma tela em branco por algum tempo.

Você pode melhorar a situação atrasando a renderização de componentes filhos:

 <template> <div> <h3>I'm an heavy page</h3> <template v-if="defer(2)"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="defer(3)" class="super-heavy" :n="9999999"/> </div> </template> <script> import Defer from '@/mixins/Defer' export default { mixins: [ Defer() ] } </script> 

Aqui, a função defer (n) retorna n quadros falsos. após o que sempre retorna verdadeiro. É usado para adiar o processamento de parte do modelo por vários quadros, dando assim ao navegador a capacidade de desenhar a interface.

Como isso funciona? Como escrevi acima, se a condição na diretiva v-if for falsa, o Vue ignorará completamente parte do modelo.

No primeiro quadro da animação, obtemos:

 <template> <div> <h3>I'm an heavy page</h3> <template v-if="false"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="false" class="super-heavy" :n="9999999"/> </div> </template> 

O Vue simplesmente monta o cabeçalho e o navegador o exibe e solicita um segundo quadro. Neste ponto, o adiamento (2) começará a retornar true

 <template> <div> <h3>I'm an heavy page</h3> <template v-if="true"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="false" class="super-heavy" :n="9999999"/> </div> </template> 

O Vue criará 10 componentes filhos e os montará. O navegador os exibirá e solicitará o próximo quadro, no qual adiar (3) retornará verdadeiro.

Assim, criamos um componente pesado que é processado gradualmente, dando ao navegador a capacidade de exibir as partes já montadas do modelo, o que melhorará muito o UX, pois a interface não parecerá travada.

Código de adiamento do misturador:

 export default function (count = 10) { return { data () { return { displayPriority: 0 } }, mounted () { this.runDisplayPriority() }, methods: { runDisplayPriority () { const step = () => { requestAnimationFrame(() => { this.displayPriority++ if (this.displayPriority < count) { step() } }) } step() }, defer (priority) { return this.displayPriority >= priority } } } } 

Carregamento lento de componentes


Agora vamos falar sobre a importação de componentes filhos:

 import Heavy from 'Heavy.js' export default { components: { Heavy } } 

Na importação tradicional, o componente filho é carregado imediatamente, à medida que o navegador atinge a instrução de importação. Ou, se você usar o coletor, seu componente filho será incluído no pacote geral.

No entanto, se o componente filho for muito grande, faz sentido carregá-lo de forma assíncrona.
É muito fácil de fazer:

 const Heavy = () => import('Heavy.js') export default { components: { Heavy } } 

É tudo o que você precisa . O Vue pode trabalhar com componentes de carregamento preguiçosos fora da caixa. Ele fará o download do componente quando necessário e o exibirá assim que estiver pronto. Você pode usar essa abordagem para carregamento lento em qualquer lugar.

Se você usar o coletor, tudo sobre o Heavy.js será retirado em um arquivo separado. Assim, você reduzirá o peso dos arquivos durante o download inicial e aumentará a velocidade de exibição.

Visualizações preguiçosas de carregamento


O carregamento lento é muito útil no caso de componentes usados ​​para rotas. Como você não precisa baixar todos os componentes para rotas ao mesmo tempo, recomendo que você sempre use esta abordagem:

 const Home = () => import('Home.js') const About = () => import('About.js') const Contacts = () => import('Contacts.js') new VueRouter({ routes: [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/contacts', component: Contacts }, ] }) 

Cada um desses componentes será carregado apenas quando o usuário solicitar a rota especificada pela primeira vez. E não antes.

Componentes dinâmicos


Da mesma forma, você pode usar facilmente o carregamento lento com componentes dinâmicos:

 <template> <div> <component :is="componentToShow"/> </div> </template> <script> export default { props: ['value'], computed: { componentToShow() { if (this.value) { return () => import('TrueComponent.js') } return () => import('FalseComponent.js') } } } </script> 

Novamente, cada componente será carregado apenas quando for necessário.

Trabalhar com Vuex


Evite grandes confirmações


Imagine que você precisa armazenar uma grande variedade de dados no armazenamento:

 fetchItems ({ commit }, { items }) { commit('clearItems') commit('addItems', items) //   10000  } 

O problema é esse. Todas as confirmações são operações síncronas. Isso significa que o processamento de uma grande matriz bloqueará sua interface pela duração do trabalho.

Para resolver esse problema, você pode dividir a matriz em partes e adicioná-las uma de cada vez, dando tempo ao navegador para desenhar um novo quadro:

 fetchItems ({ commit }, { items, splitCount }) { commit('clearItems') const queue = new JobQueue() splitArray(items, splitCount).forEach( chunk => queue.addJob(done => { //       requestAnimationFrame(() => { commit('addItems', chunk) done() }) }) ) //      await queue.start() } 

Quanto menores as partes que você adicionar dados ao armazenamento, mais suave será a interface e maior o tempo total para concluir a tarefa.

Além disso, com essa abordagem, você pode exibir um indicador de download para o usuário. O que também melhorará significativamente sua experiência com o aplicativo.

Desative a reatividade onde não for necessária


E o último exemplo de hoje. Temos uma tarefa semelhante: adicionamos uma matriz de objetos muito grandes ao armazenamento com vários níveis de aninhamento:

 const data = items.map( item => ({ id: uid++, data: item, // <--    vote: 0 }) ) 

O fato é que o Vue percorre recursivamente todos os campos aninhados e os torna reativos.Se você tiver muitos dados, isso pode ser caro, mas é muito mais importante - desnecessário.

Se seu aplicativo for construído de forma que dependa apenas do objeto de nível superior e não faça referência a dados reativos em algum lugar vários níveis abaixo, você poderá desativar a reatividade, economizando assim o Vue de um monte de trabalho desnecessário:

 const data = items.map( item => optimizeItem(item) ) function optimizeItem (item) { const itemData = { id: uid++, vote: 0 } Object.defineProperty(itemData, 'data', { //    "-" configurable: false, value: item }) return itemData } 

Source: https://habr.com/ru/post/pt483042/


All Articles