Saltar al contenido

Gestión del Estado

¿Qué es la Gestión del Estado?

Técnicamente, cada instancia de componente Vue ya "gestiona" su propio estado reactivo. Tomemos un componente de contador simple como ejemplo:

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

// estado
const count = ref(0)

// acciones
function increment() {
  count.value++
}
</script>

<!-- vista -->
<template>{{ count }}</template>
vue
<script>
export default {
  // estado
  data() {
    return {
      count: 0
    }
  },
  // acciones
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

<!-- vista -->
<template>{{ count }}</template>

Es una unidad autocontenida con las siguientes partes:

  • El estado, la fuente de verdad que impulsa nuestra aplicación;
  • La vista, un mapeo declarativo del estado;
  • Las acciones, las posibles formas en que el estado podría cambiar en reacción a las entradas del usuario desde la vista.

Esta es una representación simple del concepto de "flujo de datos unidireccional":

diagrama de flujo de estado

Sin embargo, la simplicidad comienza a romperse cuando tenemos múltiples componentes que comparten un estado común:

  1. Múltiples vistas pueden depender de la misma porción de estado.
  2. Las acciones de diferentes vistas pueden necesitar mutar la misma porción de estado.

Para el primer caso, una posible solución es "elevar" el estado compartido a un componente ancestro común, y luego pasarlo hacia abajo como props. Sin embargo, esto rápidamente se vuelve tedioso en árboles de componentes con jerarquías profundas, llevando a otro problema conocido como Propagación de Props.

Para el segundo caso, a menudo nos encontramos recurriendo a soluciones como acceder a instancias de padre/hijo directas a través de template refs, o intentar mutar y sincronizar múltiples copias del estado a través de eventos emitidos. Ambos patrones son frágiles y rápidamente llevan a código inmanejable.

Una solución más simple y directa es extraer el estado compartido de los componentes y gestionarlo en un singleton global. Con esto, nuestro árbol de componentes se convierte en una gran "vista", ¡y cualquier componente puede acceder al estado o desencadenar acciones, sin importar dónde se encuentre en el árbol!

Gestión de Estado Simple con la API de Reactividad

En la Options API, los datos reactivos se declaran utilizando la opción data(). Internamente, el objeto devuelto por data() se hace reactivo a través de la función reactive(), que también está disponible como una API pública.

Si tienes una porción de estado que debe ser compartida por múltiples instancias, puedes usar reactive() para crear un objeto reactivo, y luego importarlo en múltiples componentes:

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

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

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

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

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

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

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

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

Ahora, cada vez que el objeto store se muta, tanto <ComponentA> como <ComponentB> actualizarán sus vistas automáticamente: ahora tenemos una única fuente de verdad.

Sin embargo, esto también significa que cualquier componente que importe store puede mutarlo como desee:

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

Si bien esto funciona en casos simples, el estado global que puede ser mutado arbitrariamente por cualquier componente no será muy mantenible a largo plazo. Para asegurar que la lógica de mutación de estado esté centralizada como el propio estado, se recomienda definir métodos en el store con nombres que expresen la intención de las acciones:

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

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

TIP

Ten en cuenta que el manejador de clic usa store.increment() con paréntesis; esto es necesario para llamar al método con el contexto this adecuado, ya que no es un método de componente.

Aunque aquí estamos usando un único objeto reactivo como store, también puedes compartir estado reactivo creado usando otras APIs de Reactividad como ref() o computed(), o incluso devolver estado global desde un Composable:

js
import { ref } from 'vue'

// estado global, creado en el ámbito del módulo
const globalCount = ref(1)

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

  return {
    globalCount,
    localCount
  }
}

El hecho de que el sistema de reactividad de Vue esté desacoplado del modelo de componentes lo hace extremadamente flexible.

Consideraciones de SSR

Si estás construyendo una aplicación que aprovecha el Renderizado del Lado del Servidor (SSR), el patrón anterior puede llevar a problemas debido a que el store es un singleton compartido entre múltiples solicitudes. Esto se discute con más detalles en la guía de SSR.

Pinia

Si bien nuestra solución de gestión de estado hecha a mano será suficiente en escenarios simples, hay muchas más cosas a considerar en aplicaciones de producción a gran escala:

  • Convenciones más sólidas para la colaboración en equipo
  • Integración con las Vue DevTools, incluyendo línea de tiempo, inspección en componente y depuración de viaje en el tiempo
  • Hot Module Replacement
  • Soporte para Renderizado del Lado del Servidor

Pinia es una librería de gestión de estado que implementa todo lo anterior. Es mantenida por el equipo central de Vue y funciona tanto con Vue 2 como con Vue 3.

Los usuarios existentes pueden estar familiarizados con Vuex, la anterior librería oficial de gestión de estado para Vue. Con Pinia desempeñando el mismo papel en el ecosistema, Vuex ahora está en modo de mantenimiento. Todavía funciona, pero ya no recibirá nuevas características. Se recomienda usar Pinia para nuevas aplicaciones.

Pinia comenzó como una exploración de cómo podría ser la próxima iteración de Vuex, incorporando muchas ideas de las discusiones del equipo central para Vuex 5. Finalmente, nos dimos cuenta de que Pinia ya implementa la mayoría de lo que queríamos en Vuex 5, y decidimos convertirla en la nueva recomendación en su lugar.

En comparación con Vuex, Pinia proporciona una API más simple con menos formalidades, ofrece APIs al estilo de la Composition API y, lo más importante, tiene un sólido soporte de inferencia de tipos cuando se usa con TypeScript.

Gestión del Estado
CYBER MONDAY
Get Official Certification for 60% OFF
Get Certified
00
hours
:
13
minutes
: