Ações
As ações são as equivalentes dos métodos nos componentes. Estas podem ser definidas com a propriedade actions
na defineStore()
e são perfeitas para definir a lógica do negócio:
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
// uma vez que dependemos da `this`,
// não podemos usar uma função de flecha
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
Tal como os recuperadores, as ações têm acesso à instância da memória inteira através da this
com suporte completo à tipificação (e conclusão automática ✨). Ao contrário dos recuperadores, as actions
podem ser assíncronas, podemos await
dentro das ações qualquer chamada de API ou até mesmo outras ações! Eis um exemplo usando Mande. Nota que a biblioteca que usamos não importa desde que recebamos uma Promise
, poderíamos até mesmo usar a função fetch
nativa (apenas do navegador):
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)
// deixar o componente de formulário exibir o erro
return error
}
},
},
})
Nós também estamos completamente livres para definir quaisquer argumentos que quisermos e retornar qualquer coisa. Quando chamarmos as ações, tudo será inferido automaticamente!
As ações são invocadas da mesma maneira que as funções e métodos normais:
<script setup>
const store = useCounterStore()
// chamar a ação como um método da memória
store.randomizeCounter()
</script>
<template>
<!-- Mesmo sobre o modelo de marcação -->
<button @click="store.randomizeCounter()">Randomize</button>
</template>
Acessando Outras Ações da Memória
Para usarmos uma outra memória, podemos usá-la diretamente de dentro da ação:
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 com a API de Opções
Para os seguintes exemplos, podemos assumir que a seguinte memória foi criada:
// Caminho do Ficheiro de Exemplo:
// ./src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
}
})
Usando com a setup()
Embora a API de Composição não seja para todos, a função gatilho setup()
pode facilitar o uso da Pinia dentro da API de Opções. Sem a necessidade de funções auxiliares mapeamento adicionais!
<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>
Usando sem a setup()
Se preferiríamos não usar a API de Composição, podemos usar a auxiliar mapActions()
para mapear as propriedades das ações como métodos no nosso componente:
import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
methods: {
// dar acesso ao `this.increment()` dentro do componente
// o mesmo que chamar a partir de `store.increment()`
...mapActions(useCounterStore, ['increment'])
// o mesmo que acima exceto que o regista como `this.myOwnName()`
...mapActions(useCounterStore, { myOwnName: 'increment' }),
},
}
Subscrevendo às Ações
É possível observar as ações e seus resultados com store.$onActions()
. A função de resposta passada à esta é executada antes da própria ação. after
manipula as promessas e permite-nos executar uma função depois da ação resolver-se. Duma maneira semelhante, onError
permite-nos executar uma função se a ação lançar um erro ou rejeitar-se. Estas são úteis para rastrear os erros durante a execução, semelhante a esta dica na documentação da Vue.
Eis um exemplo que regista antes de executar as ações e depois de resolverem-se ou rejeitarem-se:
const unsubscribe = someStore.$onAction(
({
name, // nome da ação
store, // instância da memória, o mesmo que `someStore`
args, // vetor de parâmetros passados à ação
after, // disparar depois do retorno ou resolução da ação
onError, // disparar se lançar-se ou rejeitar-se
}) => {
// uma variável partilhada para esta chamada de ação especifica
const startTime = Date.now()
// isto acionará antes duma ação na `store` ser executada
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// isto acionará se a ação for bem-sucedida e
// depois de ser executada completamente.
// esta espera por qualquer retorno prometido
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// isto acionará se a ação lançar-se ou
// retornar uma promessa que rejeita-se
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// remover manualmente o ouvinte
unsubscribe()
Por padrão, as subscrições da ação estão vinculadas ao componente onde foram adicionadas (se a memória estiver dentro duma setup()
do componente). Querendo dizer que, serão removidas automaticamente quando o componente for desmontado. Se também quisermos as preservar depois do componente ser desmontado, passamos true
como segundo argumento para separar a subscrição da ação do componente atual:
<script setup>
const someStore = useSomeStore()
// esta subscrição será preservada mesmo depois
// do componente ser desmontado
someStore.$onAction(callback, true)
</script>