Acciones
Las acciones son el equivalente de los métodos en los componentes. Pueden ser definidos con la propiedad actions
en defineStore()
y son perfectas para definir lógica de negocio:
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
// como dependemos de `this`, no podemos usar funciones de flecha
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
// como dependemos de `this`, no podemos usar funciones de flecha
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
Como los getters, las acciones obtienen acceso a toda la instancia del almacén a través de this
con soporte de tipado completo (y autocompletado ✨). Al contrario que los getters, las acciones
pueden ser asíncronas, ¡puedes usar await
dentro de acciones que son llamadas por cada API o incluso otras acciones! Aquí tienes un ejemplo usando Mande. Cabe aclarar que no importa la librería que uses siempre y cuando obtengas una promesa
, incluso puedes usar la función nativa fetch
(sólo en navegadores):
import { mande } from 'mande'
const api = mande('/api/users')
export const useUsers = defineStore('users', {
state: () => ({
userData: null,
// ...
}),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
// deja al componente formulario mostrar el error
return error
}
},
},
})
import { mande } from 'mande'
const api = mande('/api/users')
export const useUsers = defineStore('users', {
state: () => ({
userData: null,
// ...
}),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
// deja al componente formulario mostrar el error
return error
}
},
},
})
También eres libre de poner cuantos argumentos quieras y no devolver nada. Cuando llames a las acciones ¡todo será deducido automáticamente!
Las acciones son llamadas cómo las funciones o los métodos normales:
<script setup>
const store = useCounterStore()
// llama a la acción como un método del almacén
store.randomizeCounter()
</script>
<template>
<!-- Incluso en el template -->
<button @click="store.randomizeCounter()">Randomize</button>
</template>
<script setup>
const store = useCounterStore()
// llama a la acción como un método del almacén
store.randomizeCounter()
</script>
<template>
<!-- Incluso en el template -->
<button @click="store.randomizeCounter()">Randomize</button>
</template>
Acceder a acciones de otros almacenes
Para usar otro almacén puedes usarlo directamente dentro de la acción:
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
preferences: null,
// ...
}),
actions: {
async fetchUserPreferences() {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
preferences: null,
// ...
}),
actions: {
async fetchUserPreferences() {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
Uso con la API de Opciones
Para los siguientes ejemplos, puedes asumir que se ha creado el siguiente almacén:
// Ruta de ejemplo:
// ./src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
}
})
// Ruta de ejemplo:
// ./src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
}
})
Con setup()
Dado que la API de composición no es para todo el mundo, el hook setup()
puede hacer que trabajar con Pinia sea más fácil con la API de opciones. ¡No necesitas funciones map helper adicionales!
<script>
import { useCounterStore } from '../stores/counter'
export default defineComponent({
setup() {
const counterStore = useCounterStore()
return { counterStore }
},
methods: {
incrementAndPrint() {
this.counterStore.increment()
console.log('New Count:', this.counterStore.count)
},
},
})
</script>
<script>
import { useCounterStore } from '../stores/counter'
export default defineComponent({
setup() {
const counterStore = useCounterStore()
return { counterStore }
},
methods: {
incrementAndPrint() {
this.counterStore.increment()
console.log('New Count:', this.counterStore.count)
},
},
})
</script>
Sin setup()
Si prefieres no usar la API de composición puedes usar el helper mapActions()
para mapear las propiedades de las acciones como métodos en tus componentes:
import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
methods: {
// da acceso a this.increment() dentro del componente
// al igual que llamarlo desde store.increment()
...mapActions(useCounterStore, ['increment']),
// lo mismo de arriba pero registrándolo como this.myOwnName()
...mapActions(useCounterStore, { myOwnName: 'increment' }),
},
}
import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
methods: {
// da acceso a this.increment() dentro del componente
// al igual que llamarlo desde store.increment()
...mapActions(useCounterStore, ['increment']),
// lo mismo de arriba pero registrándolo como this.myOwnName()
...mapActions(useCounterStore, { myOwnName: 'increment' }),
},
}
Suscribirse a acciones
Es posible observar acciones y sus resultados con store.$onAction()
. La callback que se le pasa es ejecutada antes que la propia acción. after
se encarga de manejar las promesas y te permite ejecutar una función después de resolver la acción. De forma similar, onError
te permite ejecutar una función si la acción lanza un error o es rechazada. Estos son útiles para seguir errores en tiempo de ejecución, parecido a este tip en la documentación de Vue.
Aquí tienes un ejemplo de los registros antes de ejecutar acciones y después de que sean resueltas/rechazadas.
const unsubscribe = someStore.$onAction(
({
name, // nombre de la acción
store, // instancia del almacén, igual que `someStore`
args, // array de parámetros pasados a la acción
after, // hook para cuando la acción devuelva o se resuelva
onError, // hook por si la acción lanza un error o es rechazada
}) => {
// una variable compartida para esta llamada de acción específica
const startTime = Date.now()
// esto se activará antes de que una acción en el `almacén` sea ejecutada
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// esto se activará si la acción es exitosa y tras haber terminado su
// ejecución y espera por cualquier promesa retornada
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// esto se activará si la acción lanza un error o devuelve una promesa
// rechazada
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// eliminar el listener manualmente
unsubscribe()
const unsubscribe = someStore.$onAction(
({
name, // nombre de la acción
store, // instancia del almacén, igual que `someStore`
args, // array de parámetros pasados a la acción
after, // hook para cuando la acción devuelva o se resuelva
onError, // hook por si la acción lanza un error o es rechazada
}) => {
// una variable compartida para esta llamada de acción específica
const startTime = Date.now()
// esto se activará antes de que una acción en el `almacén` sea ejecutada
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// esto se activará si la acción es exitosa y tras haber terminado su
// ejecución y espera por cualquier promesa retornada
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// esto se activará si la acción lanza un error o devuelve una promesa
// rechazada
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// eliminar el listener manualmente
unsubscribe()
Por defecto, las suscripciones a acciones están enlazadas al componente donde son añadidas (si el almacén está dentro de un setup()
de un componente). Esto significa que se eliminarán automáticamente cuando el componente sea desmontado. Si también quieres mantenerlas después de que el componente sea desmontado, pasa true
como segundo parámetro para separar la suscripción de la acción de su componente actual:
<script setup>
const someStore = useSomeStore()
// esta suscripción se mantendrá incluso tras desmontar el componente
someStore.$onAction(callback, true)
</script>
<script setup>
const someStore = useSomeStore()
// esta suscripción se mantendrá incluso tras desmontar el componente
someStore.$onAction(callback, true)
</script>