操作
操作相当于组件中的 方法。它们可以在 defineStore()
中使用 actions
属性定义,并且非常适合定义业务逻辑
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
// since we rely on `this`, we cannot use an arrow function
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
与 获取器 一样,操作可以通过 this
访问整个商店实例,并提供完整的类型(以及自动完成 ✨)支持。与获取器不同,actions
可以是异步的,您可以在操作中 await
任何 API 调用,甚至其他操作!以下是一个使用 Mande 的示例。请注意,只要您获得 Promise
,使用的库并不重要。您甚至可以使用本机 fetch
函数(仅限浏览器)
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)
// let the form component display the error
return error
}
},
},
})
您也可以自由设置任何您想要的参数并返回任何内容。调用操作时,所有内容都会自动推断!
操作像普通函数和方法一样被调用
<script setup>
const store = useCounterStore()
// call the action as a method of the store
store.randomizeCounter()
</script>
<template>
<!-- Even on the template -->
<button @click="store.randomizeCounter()">Randomize</button>
</template>
访问其他商店的操作
要使用另一个商店,您可以在操作中直接使用它
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')
}
},
},
})
与选项 API 一起使用
对于以下示例,您可以假设创建了以下商店
// Example File Path:
// ./src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++
},
},
})
使用 setup()
虽然 Composition API 并不适合所有人,但 setup()
钩子可以使 Pinia 在使用选项 API 时更容易使用。无需额外的映射辅助函数!
<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>
不使用 setup()
如果您希望完全不使用 Composition API,可以使用 mapActions()
辅助函数将操作属性映射为组件中的方法
import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
methods: {
// gives access to this.increment() inside the component
// same as calling from store.increment()
...mapActions(useCounterStore, ['increment']),
// same as above but registers it as this.myOwnName()
...mapActions(useCounterStore, { myOwnName: 'increment' }),
},
}
订阅操作
可以使用 store.$onAction()
观察操作及其结果。传递给它的回调在操作本身之前执行。after
处理 promise 并允许您在操作解析后执行函数。类似地,onError
允许您在操作抛出或拒绝时执行函数。这些对于在运行时跟踪错误很有用,类似于 Vue 文档中的此提示。
以下是一个在运行操作之前和操作解析/拒绝之后记录日志的示例。
const unsubscribe = someStore.$onAction(
({
name, // name of the action
store, // store instance, same as `someStore`
args, // array of parameters passed to the action
after, // hook after the action returns or resolves
onError, // hook if the action throws or rejects
}) => {
// a shared variable for this specific action call
const startTime = Date.now()
// this will trigger before an action on `store` is executed
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// this will trigger if the action succeeds and after it has fully run.
// it waits for any returned promised
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// this will trigger if the action throws or returns a promise that rejects
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// manually remove the listener
unsubscribe()
默认情况下,操作订阅绑定到添加它们的组件(如果商店位于组件的 setup()
中)。这意味着,当组件卸载时,它们将自动删除。如果您还想在组件卸载后保留它们,请将 true
作为第二个参数传递以将操作订阅从当前组件分离
<script setup>
const someStore = useSomeStore()
// this subscription will be kept even after the component is unmounted
someStore.$onAction(callback, true)
</script>