Estado Centralizado con Vue y Vuex / Pinia

8 de junio de 2022

El manejo de estado de forma centralizada (también llamado fuente de la verdad ) es algo de lo que disponen la mayoría de frameworks de frontend modernos.

En el ecosistema de Vue.js los más populares son Vuex y Pinia, mientras que por la parte de React, el más popular es Redux.

Aunque puedan parecer un poco confusos y pesados al principio, en este articulo lo vamos a explicar de forma sencilla para que tú también puedas empezar a usarlo en tus proyectos.

⚠️ ¿Qué problema queremos solucionar?

Normalmente en Vue el paso de información entre componentes se hace de dos maneras:

Ahora bien, si queremos pasar un dato a un componente hermano, ese componente tendrá que emitir un evento y luego el padre tendrá que pasar esa información como propiedad hacia el componente destinatario.

En el siguiente diagrama se muestra en rojo el recorrido por el que tendría que pasar nuestro dato en caso de querer pasar desde el componente C hasta el componente ABZ.

Diagrama Vue


Como podemos ver, conforme nuestras aplicaciones crezcan en tamaño y en complejidad, este sistema se puede llegar a complicar bastante, llegando incluso a que nuestro código quede mal estructurado y sea poco mantenible en el tiempo. Pasando a convertirse en lo que conocemos como código spaghetti.

Piensa en cómo gestionarías los datos para controlar cosas como: información sobre el layout, el idioma de la aplicación, el modo dark o la información del usuario conectado, que se usarán a lo largo de la aplicación.

¿Complicado verdad? Aquí es donde nos ayuda tener un estado centralizado.

🎓 Flux - el inicio

Como habíamos introducido antes, una de las librerías más conocidas de manejo de estado es Redux, y aunque se suele usar junto a React, realmente es agnóstico al framework que quieras usar.

El motivo por el cual Redux y Vuex son muy similares es que ambos se basan en el patrón Flux, un patrón de diseño basado en tener una fuente de la verdad y un flujo de información en una sola dirección. Para conocer más detalles sobre este patrón podemos encontrar más información en este enlace.

Aquí tenemos un esquema visual que resume como funciona el patrón Flux:

Diagrama Flux

No te preocupes si no lo entiendes, explicaremos los conceptos en detalle más adelante.

🛠️ Sin usar librerías externas

Muchas veces tenemos claro que el alcance de nuestra aplicación no será muy extenso, pero aun así nos vendría bien tener un sistema para gestionar información de forma centralizada sin llegar a tener que integrar Vuex o Redux.

Aunque esto no suele ser recomendable hay casos donde tiene valor.

Para esos casos, tanto React como Vue 3 lo tienen muy sencillo para implementar este patrón, utilizando sus respectivos sistemas de reactividad para compartir un objeto reactivo entre todos los componentes que lo vayan a usar.


En el caso de React también tenemos disponible la Context API, que es similar a como funciona Provide/Inject en Vue. Veamos un ejemplo:

// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the tree below.
    // Any component can read it, no matter how deep it is.
    // In this example, we're passing "dark" as the current value.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // Assign a contextType to read the current theme context.
  // React will find the closest theme Provider above and use its value.
  // In this example, the current theme is "dark".
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

En el caso de Vue, haciendo uso de su nuevo sistema de reactividad desacoplado del framework, podemos implementarlo así:

<script>
// store.js
import { reactive } from 'vue'

export const store = reactive({
  count: 0
})
</script>

<!-- ComponentA.vue -->
<script>
import { store } from './store.js'

export default {
  data() {
    return {
      store
    }
  }
}
</script>
<template>From A: {{ store.count }}</template>
<!---------------------->


<!-- ComponentB.vue -->
<script>
import { store } from './store.js'

export default {
  data() {
    return {
      store
    }
  }
}
</script>
<template>From B: {{ store.count }}</template>
<!---------------------->

Aunque ambos casos funcionan, deberían ser utilizados solo en casos muy concretos donde tenemos muy claro el alcance y tenemos controlados todos los lugares donde se va a utilizar ese "store".

📦 Vuex - el presente

Vuex es el equivalente a Redux, pero en el ecosistema Vue, de forma similar, sirve para crear y gestionar una fuente de la verdad, con el añadido de que se integra mucho mejor con Vue ya que se creó pensando en eso.

Para empezar a usar Vuex primero necesitamos entender algunos conceptos:


En este esquema extraído de la propia documentación oficial de Vuex te lo explica de forma más visual

Diagrama Vuex

Actualmente ya no se recomienda el uso de Vuex para proyectos nuevos debido a que se encuentra en estado de mantenimiento y no recibirá nuevas funcionalidades.

Puedes encontrar más detalles en este enlace.

🍍 Pinia - el futuro

Como explican en su página, Pinia comenzó como un experimento aparte de Vuex en Noviembre de 2019, donde se pretendía experimentar con el concepto de store, además de integrarlo con la Composition API de Vue 3.

Conforme el proyecto fue avanzando fue ganando funcionalidades, hasta el punto actual donde ha reemplazado a Vuex como librería oficial.

A nivel conceptual Pinia es muy similar a Vuex, las principales diferencias son estas:

Veamos un ejemplo de cómo funciona esto:

import { defineStore } from 'pinia'

export const todos = defineStore('todos', {
  state: () => ({
    /** @type {{ text: string, id: number, isFinished: boolean }[]} */
    todos: [],
    /** @type {'all' | 'finished' | 'unfinished'} */
    filter: 'all',
    // type will be automatically inferred to number
    nextId: 0,
  }),
  getters: {
    finishedTodos(state) {
      // autocompletion! ✨
      return state.todos.filter((todo) => todo.isFinished)
    },
    unfinishedTodos(state) {
      return state.todos.filter((todo) => !todo.isFinished)
    },
    /**
     * @returns {{ text: string, id: number, isFinished: boolean }[]}
     */
    filteredTodos(state) {
      if (this.filter === 'finished') {
        // call other getters with autocompletion ✨
        return this.finishedTodos
      } else if (this.filter === 'unfinished') {
        return this.unfinishedTodos
      }
      return this.todos
    },
  },
  actions: {
    // any amount of arguments, return a promise or not
    addTodo(text) {
      // you can directly mutate the state
      this.todos.push({ text, id: this.nextId++, isFinished: false })
    },
  },
})

A primera vista podemos ver que es bastante similar a como funciona Vuex.

Vamos por partes:

¡Y eso es todo, estos son los conceptos básicos para que puedas empezar a usar Vuex o Pinia en tus proyectos!


Créditos

Foto por Marcin Jozwiak en Unsplash.

Sobre el autor: Bryan de Oliveira Brettas

Desarrollador full-stack en Mimacom, enfocándose hacia el frontend sin olvidar el otro lado

Comments
Únete a nosotros