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.0Separaçã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 </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)
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 => {
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,
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', {