Skip to content

Gestão de Estado

O Que é a Gestão de Estado?

Tecnicamente, toda instância de componente de Vue já faz a "gestão" do seu próprio estado reativo. Considere o componente contador como um exemplo:

vue
<script setup>
import { ref } from 'vue'

// estado
const count = ref(0)

// ações
function increment() {
  count.value++
}
</script>

<!-- visão ou apresentação -->
<template>{{ count }}</template>
vue
<script>
export default {
  // estado
  data() {
    return {
      count: 0
    }
  },
  // ações
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

<!-- visão ou apresentação -->
<template>{{ count }}</template>

É uma unidade auto-contida com as seguintes partes:

  • O estado, a fonte de verdade que orienta a nossa aplicação.
  • A visão (ou apresentação), um mapeamento declarativo do estado;
  • As ações, as maneiras possíveis que o estado poderia mudar em reação as entradas do utilizador da visão (ou apresentação).

Isto é uma representação simples do conceito do "fluxo de dados de uma via":

diagrama do fluxo de estado

No entanto, a simplicidade começa a falhar quando temos vários componentes que partilham um estado comum:

  1. Várias visões que podem depender da mesma parte do estado.
  2. Ações de visões diferentes que podem precisar mudar a mesma parte do estado.

Para o primeiro caso, uma possível solução é "elevando" o estado partilhado para cima para um componente ancestral comum, e depois passar para baixo como propriedades. No entanto, isto torna-se rapidamente entediante em árvores de componente com hierarquias profundas, levando para um outro problema conhecido como Perfuração de Propriedade.

Para o segundo caso, frequentemente encontramos-nos recorrendo a soluções tais como alcançar instâncias pai/filho diretas através de referências, ou tentando alterar e sincronizar várias cópias do estado por meio de eventos emitidos. Ambos os padrões são frágeis e conduzem rapidamente a um código insustentável.

Uma solução mais simples e mais direita é extrair o estado compartilhado para fora dos componentes, e gerenciá-lo em um monotónico (singleton, em Inglês) global. Com isto, nossa árvore de componentes torna-se uma grande "visão", e qualquer componente pode acessar o estado ou disparar as ações, não importa onde eles estão na árvore!

Gestão de Estado Simples com API de Reatividade

Na API de Opções, os dados reativos são declarados com o uso da opção data(). Internamente, o objeto retornado por data() é tornado reativo através da função reactive(), que também está disponível como uma API pública.

Se tiveres uma parte do estado que deveria ser compartilhado por várias instâncias, poderá usar reactive() criar um objeto reativo, e então importá-lo para vários componentes:

js
// store.js
import { reactive } from 'vue'

export const store = reactive({
  count: 0
})
vue
<!-- ComponentA.vue -->
<script setup>
import { store } from './store.js'
</script>

<template>From A: {{ store.count }}</template>
vue
<!-- ComponentB.vue -->
<script setup>
import { store } from './store.js'
</script>

<template>From B: {{ store.count }}</template>
vue
<!-- ComponentA.vue -->
<script>
import { store } from './store.js'

export default {
  data() {
    return {
      store
    }
  }
}
</script>

<template>From A: {{ store.count }}</template>
vue
<!-- ComponentB.vue -->
<script>
import { store } from './store.js'

export default {
  data() {
    return {
      store
    }
  }
}
</script>

<template>From B: {{ store.count }}</template>

Agora sempre que o objeto store é alterado, ambos <ComponentA> e <ComponentB> atualizarão suas apresentações automaticamente - agora nós teremos uma fonte única de verdade.

No entanto, isto também significa que qualquer componente importando store pode modificá-lo como quiser:

template
<template>
  <button @click="store.count++">
    From B: {{ store.count }}
  </button>
</template>

Enquanto isto funciona em casos simples, o estado global que pode ser alterado arbitrariamente por qualquer componente não será muito sustentável a longo prazo. Para garantir que a lógica de mutação de estado esteja centralizada como o próprio estado, é recomendado definir métodos na memória (store, em Inglês) com nomes que expressam a intenção das ações:

js
// store.js
import { reactive } from 'vue'

export const store = reactive({
  count: 0,
  increment() {
    this.count++
  }
})
template
<template>
  <button @click="store.increment()">
    From B: {{ store.count }}
  </button>
</template>

DICA

Observe que o manipulador de clique usa store.increment() entre os parênteses - isto é necessário para chamar o método com o contexto this apropriado já que não é um método do componente.

Embora aqui estejamos usando um único objeto reativo como uma memória, também pode compartilhar o estado reativo criado com uso de outras APIs de Reatividade tal como ref() ou computed(), ou mesmo retornar o estado global a partir de um Constituível:

js
import { ref } from 'vue'

// estado global, criado no escopo do módulo
const globalCount = ref(1)

export function useCount() {
  // estado local, criado por cada componente
  const localCount = ref(1)

  return {
    globalCount,
    localCount
  }
}

O fato do sistema de reatividade da Vue estar separado do modelo do componente torna-o extremamente flexível.

Considerações de SSR

Se estiveres construindo uma aplicação que influencia a Interpretação no Lado do Servidor (SSR, sigla em Inglês), o padrão acima pode levar a problemas devido a memória ser uma monotónica (singleton, em Inglês) partilhada através de várias requisições. Isto é discutido em mais detalhes no guia da SSR.

Pinia

Enquanto a nossa solução de gestão de estado simples será suficiente em cenários simples, existem muitas outras coisas a serem consideradas em aplicações de produção em grande escala:

  • Convenções mais fortes para colaboração do time
  • Integração com as Ferramentas de Programação de Vue, incluindo a linha do tempo, inspeção dentro do componente, e a depuração capaz de viajar no tempo
  • Substituição de Módulo Instantânea
  • Suporte para Interpretação no Lado do Servidor

Pinia é uma biblioteca de gestão de estado que implementa tudo que está acima. Ela é mantida pela equipa principal da Vue, e funciona com ambas Vue 2 e Vue 3.

Os usuários existentes podem estar familiarizados com Vuex, a antiga biblioteca de gestão de estado oficial para Vue. Com a Pinia servindo o mesmo propósito no ecossistema, a Vuex está agora em modo de manutenção. Ainda funciona, mas não receberá novas funcionalidades. É recomendado usar a Pinia para as aplicações novas.

A Pinia começou como uma exploração de como seria a próxima iteração da Vuex, incorporando muitas ideias das discussões do time principal sobre a Vuex 5. Eventualmente, percebemos que a Pinia já implementa a maior parte daquilo que nós queríamos na Vuex 5, e então decidimos torná-la a nova recomendação.

Comparada a Vuex, a Pinia fornece uma API mais simples com menos cerimônia, oferece APIs no estilo da API de Composição, e mais importante, possui suporte sólido a inferência de tipo quando usada com TypeScript.

Gestão de Estado has loaded