Inicio

Qué son los módulos en Vuex

En un tutorial anterior vimos la librería Vuex y todas las bondades que trae almacenar la información en un estado global. Esto es sumamente importante en proyectos grandes, pero aún más importante es tener la información ordenada y separada, si es posible, según el ámbito al que pertenece esta información. La librería Vuex también nos brinda esta magnifica característica, la de separar nuestros estados, mutations, getters y actions en diferentes módulos diferenciados.

En este tutorial aprenderemos a separar los estados y las distintas operaciones de Vuex en diferentes módulos, además de las formas que existen para acceder a esta información.

Índice

  1. Qué son los módulos
  2. Identificando los módulos con namespaced
  3. Tu turno

1. Qué son los módulos

Los módulos nos permiten tener estados separados, para así segmentar nuestra aplicación de tal manera que los datos no se mezclen. Para realizar esto primero necesitamos un proyecto de Vuejs con Vuex instalado.

Si ya tenemos Vuex en nuestro proyecto comenzaremos creando dos módulos y registrándolos dentro de la instancia de Vuex de nuestro proyecto. Veamos cómo podemos hacer esto dentro del archivo que controla vuex store/index.js.

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const moduleA = {
  state: {
    name: 'my module A'
  },
  getters: {
    nameModuleA(state) {
      return state.name
    }
  }
}

const moduleB = {
  state: {
    name: 'my module B'
  },
  getters: {
    nameModuleB(state) {
      return state.name
    }
  }
}

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {
    firstModule: moduleA,
    secondModule: moduleB
  }
})

Ahora para poder utilizar el getter que nos traerá uno de los estados de Vuex, solo debemos utilizar el siguiente código dentro de nuestro componente.

// Para acceder al getter de moduleB
this.$store.getters.nameModuleB

// Para acceder al getter de moduleA
this.$store.getters.nameModuleA

Y si lo que queremos es ingresar directamente al estado state de algún modulo, podemos utilizar el siguiente código.

this.$store.state.firstModule.name

Esa no es la única forma de declarar un modulo, a continuación mostramos cómo declarar módulos en archivos diferentes.

// Definimos nuestro modulo dentro de store/modules/moduloA.js
export const state = {
  name: 'mi modulo A'
}
export const getters = {
  nameModuleA(state) {
    return state.name
  }
}
// Asi importamos desde store/index.json
import Vue from 'vue'
import Vuex from 'vuex'
import * as moduleA from '@/store/modules/moduleA.js'
import * as moduleB from '@/store/modules/moduleB.js'

Vue.use(Vuex)


export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {
    primerModulo: moduleA,
    segundoModulo: moduleB
  }
})

Y si lo que queremos usar es el mapState para traer algún valor, lo declaramos de la siguiente forma.

<template>
  <div>
    <h4>{{name}}</h4>
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  name: 'About',
  components: {},
  methods: {},
  computed: {
    ...mapState({
       name: state => state.segundoModulo.name
    })
  }
}
</script>

Eso es básicamente lo que necesitamos para utilizar los módulos. Si el proyecto lo amerita, nuestros módulos crecerán tanto como se necesite, veamos el siguiente ejemplo, donde utilizamos servicios para traer datos.

import EventService from '@/services/EventService.js'
    
export const state = {
  events: [],
  eventsTotal: 0,
  event: {}
}

// Las mutaciones modifican los estados
export const mutations = {
  ADD_EVENT(state, event) {
    state.events.push(event)
  },
  SET_EVENTS(state, events) {
    state.events = events
  },
  SET_EVENTS_TOTAL(state, eventsTotal) {
    state.eventsTotal = eventsTotal
  },
  SET_EVENT(state, event) {
    state.event = event
  }
}

// Los actions traen datos desde el servicio
export const actions = {

  // El commit nos permite acceder al mutation y enviarle datos
  // Este action hace post al API y modifica el estado agregado un evento
  createEvent({ commit }, event) {
    return EventService.postEvent(event).then(() => {
      commit('ADD_EVENT', event)
    })
  },
  // Este action trae datos del API y modifica el estado
  fetchEvents({ commit }, { perPage, page }) {
    EventService.getEvents(perPage, page)
      .then(response => {
        commit('SET_EVENTS_TOTAL', parseInt(response.headers['x-total-count']))
        commit('SET_EVENTS', response.data)
      })
      .catch(error => {
        console.log('There was an error:', error.response)
      })
  },
  // Este action trae un evento especifico y modifica el estado
  fetchEvent({ commit, getters }, id) {
    var event = getters.getEventById(id)
    if (event) {
      commit('SET_EVENT', event)
    } else {
      EventService.getEvent(id)
        .then(response => {
          commit('SET_EVENT', response.data)
        })
        .catch(error => {
          console.log('There was an error:', error.response)
        })
    }
  }
}

// Los getters llevan datos del estado al componente
export const getters = {
  getEventById: state => id => {
    return state.events.find(event => event.id === id)
  }
}

Por otra parte, si lo que queremos es llamar a un action de un modulo, lo que tenemos que hacer es utilizar un método de la siguiente forma.

<template>
  <div>
    <h4>Title</h4>
    <button @click="callAction">Click me</button>
  </div>
</template>
<script>
export default {
  name: 'About',
  data() {
    return {
      myMessage: 'Hello'
    }
  },
  components: {},
  methods: {
    callAction() {
      this.$store.dispatch('createEvent')
    },
  }
}
</script>

Pero aquí ya tenemos un problema, como puedes ver no fue necesario mencionar al módulo en el cual se encuentra el action. Por lo que si tenemos dos actions con el mismo nombre en diferentes módulos, tendríamos un grabe problema, ya que no habría forma de identificar correctamente a los actions. Aquí entra en juego un tema que veremos más adelante, los módulos.

Si estamos en un modulo y queremos acceder al estado de otro modulo mediante un action. Lo que tenemos que hacer es lo siguiente.
// Utilizamos rootState para ingresar al estado raiz
export const actions = {
  createEvent({ rootState }) {
    console.log(rootState.secondModule.name)
  },
}

Como podemos ver a partir de rootState podemos acceder al estado raíz que contiene a los demás módulos. De esta forma solo usamos rootState.secondModule para acceder al segundo modulo.

Si por el contrario queremos acceder a los actions de los otros modulos, lo hacemos de la siguiente forma.
export const actions = {
  createEvent({ dispatch }) {
    dispatch('myAction')
  },
}

Como puedes ver, al action se accede de forma directa. Ahora veamos como se accede al action de ese modulo desde un componente.

<template>
  <div>
    <button @click="create">Click</button>
  </div>
</template>
<script>
import { mapActions } from 'vuex'

export default {
  name: 'About',
  data() {
    return {
      myName: 'Andy',
    }
  },
  components: {},
  methods: {
    ...mapActions({
      create: 'createEvent'
    })
  }
}
</script>

Igual que antes, accedemos al action createEvent sin mencionar el nombre del modulo al que pertenece. Así que ahora veamos cómo solucionar esto.

2. Identificando los módulos con namespaced

Como puedes ver, para llamar a un action o un getter no hay forma de distinguir el modulo al que pertenece. Para poder hacer esta distinción utilizamos los namespaces. Esto lo habilitamos dentro de nuestro modulo de la siguiente forma.

import EventService from '@/services/EventService.js'

export const namespaced = true

export const state = {
  events: [],
    ...

El simple hecho de colocar eso, nos permitirá llamar a un estado o action de nuestro segundo modulo, de la siguiente forma.

<template>
  <div>
    <h1>{{name}}</h1>
    <button @click="myAction">Click</button>
  </div>
</template>
<script>
import { mapState, mapActions } from 'vuex'

export default {
  name: 'About',
  data() {
    return {
      myName: 'Andy',
    }
  },
  components: {},
  computed: {
    ...mapState('secondModule', ['name'] )
  },
  methods: {
    ...mapActions('secondModule', ['myAction'])
  }
}
</script>

Y si tenemos estados y actions con el mismo nombre en diferentes módulos podemos hacer lo siguiente para diferenciarlos. Si

<template>
  <div>
    <h1>{{nameA}}</h1>
    <h1>{{nameB}}</h1>
    <button @click="primero">Click</button>
    <button @click="segundo">Click</button>
  </div>
</template>
<script>
import {  mapState, mapActions } from 'vuex'

export default {
  name: 'About',
  data() {
    return {
      myName: 'Andy',
    }
  },
  components: {},
  computed: {
    ...mapState({
      nameA: state => state.firstModule.name,
      nameB: state => state.secondModule.name
    })
  },
  methods: {
    ...mapActions({
      primero: 'firstModule/createEvent',
      segundo: 'secondModule/createEvent'
    })
  }
}
</script>

Como puedes ver, ya es más fácil diferenciar los actions y estados de diferentes módulos y esto nos puede ayudar a tener un código más limpio.

Por otro lado, si estamos en un modulo y queremos llamar al action de otro modulo, debemos hacer lo siguiente.
actions: {
  createEvent({ commit, dispatch }, event) {
    dispatch('moduleName/actionToCall', null, { root: true })
  }

Solo debemos agregar el parámetro { root: true } para acceder al estado raíz y de ahí partir hacia el modulo que estamos buscando.

Un elemento del que menos hablamos en este tutorial son los getters, pero la forma en la que los llamamos es la misma que la que usamos para los estados.

computed: {
  getEventById() {
    return this.$store.getters['moduleName/getEventById']
  }
}

Y de esta otra forma si es que utilizamos mapGetters.

 computed: mapGetters('moduleName', ['getEventById'])

3. Tu turno

Acabamos de llegar al final de este tutorial, ahora el reto es crear proyectos donde hagas uso de diferentes módulos para guardar diferentes tipos de información. Utiliza al máximo todo lo que aprendimos aquí.