Skip to content

Gestão de Estado

O Que é a Gestão de Estado?

Tecnicamente, cada instância de componente da Vue já "gere" o seu próprio estado reativo. Tomemos como exemplo um simples componente de contador:

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

// estado
const count = ref(0)

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

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

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

É uma unidade autónoma com as seguintes partes:

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

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

diagrama do fluxo de estado

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

  1. Várias visões que podem depender do mesmo pedaço de estado.
  2. As ações de diferentes visões podem precisar de alterar o mesmo pedaço de estado.

Para o primeiro caso, uma possível solução alternativa é "elevar" o estado partilhado até um componente ancestral comum e depois passá-lo como propriedades. No entanto, isto torna-se rapidamente tedioso em árvores de componentes com hierarquias profundas, levando a outro problema conhecido como Perfuração de Propriedade.

Para o segundo caso, muitas vezes recorremos a soluções como procurar instâncias diretas de pais ou filhos por meio de referências de modelos de marcação, ou tentar 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 código impossível de manter.

Uma solução mais simples e direta é extrair o estado partilhado dos componentes e geri-lo num objeto autónomo global. Com isto, a nossa árvore de componentes transforma-se numa grade "visão" e qualquer componente pode aceder ao estado ou desencadear ações, independentemente da sua posição na árvore!

Gestão de Estado Simples com API de Reatividade

Na API de Opções, os dados reativos são declarados usando a 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 tivermos um pedaço de estado que deve ser partilhado por várias instâncias, podemos usar reactive() para criar um objeto reativo, e depois importá-lo em 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 for alterado, tanto <ComponentA> como <ComponentB> atualizarão suas visões automaticamente — temos agora uma única fonte de verdade.

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

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

Embora isto funcione em casos simples, o estado global que pode ser arbitrariamente alterado por qualquer componente não será muito sustentável a longo prazo. Para garantir que a lógica de alteração do estado está centralizada, tal como o próprio estado, recomenda-se a definição de métodos na memória 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

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

Embora aqui estejamos usando um único objeto reativo como memória, também podemos partilhar o estado reativo criado usando outras APIs de Reatividade, como ref() ou computed(), ou mesmo retornar o estado global duma Função de Composição:

js
import { ref } from 'vue'

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

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

  return {
    globalCount,
    localCount
  }
}

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

Considerações da Interpretação do Lado do Servidor

Se estivermos construindo uma aplicação que recorre à Interpretação do Lado do Servidor (SSR), o padrão acima pode dar origem a problemas pelo fato da memória ser um simples elemento partilhado entre várias requisições. Esta questão é abordada mais detalhadamente no guia da Interpretação do Lado do Servidor.

Pinia

Embora a nossa solução de gestão de estado manual satisfaça cenários simples, existe muitos outros aspetos a considerar em aplicações de produção em grande escala:

  • Convenções mais fortes para colaboração em equipa
  • Integração com as Ferramentas de Programação da Vue, incluindo linha de tempo, inspeção no componente e depuração de viagens no tempo
  • Substituição de Módulo Instantânea
  • Suporte à Interpretação do Lado do Servidor

Pinia é uma biblioteca de gestão de estado que implementa todos itens acima. É mantida pela equipa principal da Vue, e funciona tanto com a Vue 2 como com a Vue 3.

Os utilizadores existentes podem estar familiarizados com a Vuex, a anterior biblioteca oficial de gestão de estados da Vue. Com a Pinia a desempenhar o mesmo papel no ecossistema, a Vuex está agora em modo de manutenção. Ela ainda funciona, mas não receberá mais novos recursos. Recomenda-se o uso da Pinia para as novas aplicações.

A Pinia começou como uma exploração do que poderia ser a próxima iteração da Vuex, incorporando muitas ideias das discussões da equipa principal para a Vuex 5. Eventualmente, percebemos que a Pinia já implementa a maioria do que queríamos na Vuex 5, e decidimos fazer dela a nova recomendação.

Em comparação com a Vuex, a Pinia fornece uma API mais simples com menos cerimónias, oferece APIs de estilo da API de Composição e, mais importante, tem um suporte sólido de inferência de tipos quando usada com a TypeScript.

Gestão de Estado has loaded