Vuex
- Def.: Librería para manejar el estado global (el local es de los propios componentes)
- Para datos que se usan en varios componentes (ej. carrito de compra, autenticación de usuario, etc.)
- Pasos para su uso:
-
import {createStore} from vuex en App.js
-
creamos una constante llamada store que sea igual a la función createStore(), la cual
contiene un objeto con el método state() dentro, que a su vez retorna otro objeto con las
propiedades/datos del estado global:
-
const store = createStore({
state () {
return {
counter: 0
}
}
})
- app.use(store) (la constante que hemos creado arriba) en App.js
- En el componente correspondiente, si hacemos $store.state.counter accederíamos al counter
de nuestro store arriba
-
podríamos crear un método dentro del compo que fuese
addOne() {
this.$store.state.counter++;
}
y asignárselo a un button para ir ampliando el counter
- Del mismo modo, se podría hacer eso pero en vez de sumar 1, sumar 2 en otro componente
- Ambas opciones serían incorrectas --> no es correcto manipular el state del store
en los componentes. Para ello utilizamos mutations.
-
mutations
-
Def.: Contienen la lógica para modificar/mutar el state
-
Deben ser síncronas --> no deben contener promesas/código asíncrono, porque:
-
tienen que ejecutarse rápido para manterner el state actualizado
-
si contiene código asíncrono, puede tardar hasta que llegue la respuesta, y ese
tiempo de retraso puede provocar que quien invoque a la mutation utilice el
estado anterior (sin tiempo de haber sido mutado) y no el más reciente
-
Ej.:
const store = createStore({
state () {
return {
counter: 0
}
},
mutations: {
increment(state) {
state.counter++;
}
}
})
-
No se deben utilizar directamente en los componentes, sino en las actions (dentro del store)
- Para utilizar la mutation de arriba, creamos un método
que la llame (utilizando commit):
-
addOne() { this.$store.commit('increment') }
-
le pasamos la mutation como string (se tiene que llamar igual)
De esta forma, la lógica para mutar el store se
queda dentro del propio store y no en los
componentes
-
Se le pueden pasar parámetros (además del state), por ejemplo:
-
mutations: {
increase(state, payload) {
state.counter = state.counter + payload.value; (aquí payload sería un
objeto, pero puede ser cualquier tipo de dato)
}
}
(Se puede llamar payload o lo que sea, al igual que la propiedad value se podría
haber llamado diferente)
-
En su invocación -->
addTen() { this.$store.commit('increase', { value: 10 } } (como el segundo
parámetro era un objeto con una propiedad value, le pasamos eso con
el valor que queramos)
-
Otra opción: se puede pasar un objeto a commit:
-
addTen() { this.$store.commit({
type: 'increase', (nombre de la mutation)
value: 10 (parámetro que hemos creado en la mutation)
}) }
-
getters
-
Def.: Son, por así decirlo, las computadas del store
-
Tienen que retornar siempre un valor
-
Como se utilizan para retornar/mostrar una información, deben ser llamados en los
componentes dentro de las computadas (a diferencia de las mutations/actions, que
se llaman en los methods)
-
Al acceder a ellos en los componentes, no hay que utilizar paréntesis. Ej:
-
computed: {
example() {
return this.$store.getters.finalCounter;
}
}
-
Siempre van a tener como argumento:
-
Como mínimo el state
-
Como posible segunda opción otro getter
-
Ej. con ambas opciones:
-
mutations: {
...
},
getters: {
finalCounter(state) {
return state.counter * 3;
},
secondCounter(state, getters) {
const previousCounter = getters.finalCounter;
if (previousCounter < 0) {
return 0;
}
else {
return previousCounter;
}
}
}
-
Como se puede apreciar, en el secondCounter usamos getter pero no el
state, por ello los
argumentos serían así:
-
getters: {
finalCounter(_, getters) {
const previousCounter = getters.finalCounter;
if (previousCounter < 0) {
return 0;
}
else {
return previousCounter;
}
}
}
(El guión bajo es una convención usada para indicar que necesitamos
ocupar el espacio del primer argumento para poder utilzar el segundo)
-
mapGetters
-
En el compo, se pueden traer "de golpe" todos los getters (o los que se quiera usar) y utilizarlos llamándolos por su nombre
-
Ej. en el compo --> siempre dentro de computed:
-
Opción 1 (directamente por su nombre original):
...mapGetters(['finalCounter', 'whatever'])
-
Opción 2 (renombrándolos, mediante un objeto):
...mapGetters({
finCount: 'finalCounter',
what: 'whatever'
})
Con las actions se puede hacer lo mismo (mapActions()), pero dentro de methods
-
actions
-
Def.: intermediador entre las mutations y los componentes
-
Tienen como argumento:
-
su context
-
segundo opcional: un payload (del tipo que sea)
-
Sí pueden contener código asíncrono
-
Por tanto, pueden ejecutar una mutation por ej cuando se resuelva una promesa
-
Por ello, se suele poner el mismo nombre a la action que a la mutation a la que invoca
(ejemplo más abajo)
-
Cómo declararlas en el store:
-
mutations: {
increase(state, payload) {
state.counter = state.counter + payload.value; (aquí payload sería un
objeto, pero puede ser cualquier tipo de dato)
}
},
actions: {
increase(context) {
context.commit('increase') (aquí commiteamos la mutation
declarada arriba)
}
},
getters: {
...
}
-
Cómo llamarlas en el componente:
-
methods: {
addTen() {
this.$store.dispatch('increase')
}
}
-
modules
-
Def.: Bloques en los cuales se puede separar en store para tenerlo más ordenado.
-
El store (con la función createStore()) contiene al mismo nivel del state, mutations, etc. el objeto modules (va antes que el state). Ej.:
-
const counterModule = { state() { return {counter: 0} }, mutations: {...}, etc }
-
Ahora, en el store, "importamos" ese módulo:
-
const store = createStore({ modules: { numbers: counterModule }, state() { return {...} }, mutations: {...}, etc. })
-
Este objeto contiene los módulos que estén fuera del store, que a su vez tienen su propio state local, mutations, getters y actions, por tanto:
-
Dentro de un módulo sólo podemos/debemos acceder al state del propio módulo
-
Si lo necesitásemos, podemos añadir lo siguiente (ejemplo de un getter):
-
testAuth(state, getters, rootState, rootGetters) {
return rootState.isLoggedIn;
}
De esta forma, desde un módulo X, estaríamos accediendo al módulo de autenticación
-
Namespacing
-
Manera de separar totalmente un módulo de otro (estén o no dentro de un mismo archivo, por ej. store.js) para que no haya confusiones si se utilizan mismos nombres para actions, getters, etc.
-
Antes del state, se pondría -->
namespaced: true,
-
Al hacer esto, habría que tener en cuenta la palabra con la que, en el store, hemos llamado al módulo importado (numbers en el ejemplo de arriba):
-
En un componente, si queremos acceder a un getter, ya no sería con el this.$store.getters.whatever, sino:
-
this.$store.getters['numbers/whatever']
-
Si utilizamos mapGetters(), sería -->
...mapGetters('numbers', ['whatever'])
-
Dentro del store, también habría que poner delante
'numbers/loQueSea'
(en este caso) para acceder a cosas del otro módulo que hemos restringido con namespaced