Skip to content
En esta página

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:

js
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):

js
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:

vue
<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:

js
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:

js
// 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!

vue
<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:

js
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.

js
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:

vue
<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>

Lanzado bajo la Licencia MIT.