基于使用 setup() 时的用法记录
defineStore 用来定义定义 Store, 第一个参数为 Store 的id, 用于区分不同模块
state 定义数据,相当于vue中的data, 为了完整类型推理,推荐使用箭头函数;
getters 对 state中数据进行计算处理, 相当于vue中的 computed
action 相当于vue中的 method, 可以写同步和异步方法
import { defineStore } from 'pinia'
import { useOtherStore } from './other-store'
interface UserInfo {
name: string
age: number
}
interface State {
count: number;
user: UserInfo | null;
userList: UserInfo[];
}
export const useLoginStore = defineStore('main', {
// 为了完整类型推理,推荐使用箭头函数
state: ():State => {
return {
count:1,
user:null,
userList:[],
}
},
// 计算属性
getters: {
// 自动推断出返回类型是一个 number
doubleCount: (state) => state.count * 2,
// 可以用 this 来引用 getter
doublePlusOne() {
// 整个 store 的 自动补全和类型标注
return this.doubleCount + 1
},
// 可以从 getter 返回一个函数,该函数可以接受任意参数
countAddNum: (state) => {
let count = state.count; // 这里形成闭包,变量会被缓存
return (num) => state.count + num + count
},
// 访问其他 store 的 getter
otherGetter(state) {
const otherStore = useOtherStore()
return state.count + otherStore.data
},
},
// Action 相当于组件中的 method。它们也是定义业务逻辑的完美选择
actions: {
// 同步的
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
// 异步的
async registerUser(login, password) {
try {
this.user = await api.post({ login, password })
alert(`Welcome back ${this.user.name}!`)
} catch (error) {
alert(error)
// 让表单组件显示错误
return error
}
},
// 使用其他 store
async fetchUserPreferences() {
const auth = useOtherStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
});
使用 setup() 时的用法
<script setup lang="ts">
import { useLoginStore } from "@/store/useLoginStore"
const store = useLoginStore();
console.log(store.count)
script>
你可以通过 store 实例访问 state,直接对其进行读写
console.log(store.count)
直接修改
store.count++
批量修改
$patch
方法 允许你用一个 state 的补丁对象在同一时间更改多个属性
store.$patch({
count: store.count + 1,
user: null,
})
重置
使用选项式 API 时,你可以通过调用 store 的 $reset() 方法将 state 重置为初始值。
store.$reset()
可以通过 store 的 $subscribe() 方法侦听 state 及其变化。
比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次
store.$subscribe((mutation, state) => {
console.log(mutation.storeId) // main // 和 store.$id 一样
console.log(mutation.type) // 'direct' | 'patch object' | 'patch function'
// 只有 mutation.type === 'patch object'的情况下才可用
console.log(mutation.payload) // 传递给 store.$patch() 的补丁对象。
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('mainStore', JSON.stringify(state))
})
如果你想在组件卸载后依旧保留它们,设置第二个参数 { detached: true }
<script setup>
// 此订阅器即便在组件卸载之后仍会被保留
store.$subscribe(callback, { detached: true })
script>
你可以在 pinia 实例上使用 watch() 函数侦听整个 state。
watch(
pinia.state,
(state) => {
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
);
可以通过变更 pinia 实例的 state 来设置整个应用的初始 state
常用于 SSR 中的激活过程。
pinia.state.value = JSON.parse(localStorage.getItem('piniaState')) || {}
每当状态发生变化时,将整个 state 持久化到本地存储。
store.$subscribe((mutation, state) => {
localStorage.setItem('mainStore', JSON.stringify(state))
});
初始化时回显持久化数据
state: ():State => {
return {
count:1,
user:null,
userList:[],
...(JSON.parse(localStorage.getItem('mainStore')) || {})
}
},
在 App.vue
中回显缓存数据
import { useLoginStore } from "@/store/useLoginStore"
const store = useLoginStore();
store.$patch(JSON.parse(localStorage.getItem('mainStore')))
作为 store 的一个属性,你可以直接访问任何 getter(与 state 属性完全一样)
const store = useLoginStore();
console.log(store.count ) // 读取 state
console.log(store.doubleCount ) // 读取getter
Action 可以像函数或者通常意义上的方法一样被调用
const store = useLoginStore();
store.randomizeCounter()
在模板中也可以
<button @click="store.randomizeCounter()">生成随机数button>
你可以通过 store.$onAction() 来监听 action 和它们的结果。
传递给它的回调函数会在 action 本身之前执行。
const store = useLoginStore();
store.$onAction(callback)
如果你想在组件卸载后依旧保留它们,请将 true 作为第二个参数,以便将其从当前组件中分离
const store = useLoginStore();
store.$onAction(callback, true)
after 表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。
onError 允许你在 action 抛出错误或 reject 时执行一个回调函数。
这些函数对于追踪运行时错误非常有用,类似于Vue docs 中的这个提示。
这里有一个例子,在运行 action 之前以及 action resolve/reject 之后打印日志记录。
const unsubscribe = store.$onAction(
({
name, // action 名称
store, // store 实例,类似 `store`
args, // 传递给 action 的参数数组
after, // 在 action 返回或解决后的钩子
onError, // action 抛出或拒绝的钩子
}) => {
// 为这个特定的 action 调用提供一个共享变量
const startTime = Date.now()
// 这将在执行 "store "的 action 之前触发。
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 这将在 action 成功并完全运行后触发。
// 它等待着任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回一个拒绝的 promise,这将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动删除监听器
unsubscribe()
main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import { useLoginStore } from "@/store/useLoginStore"
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
// app 安装 pinia 插件后使用
const store = useLoginStore();
console.log(store.count)
App.vue
<script setup lang="ts">
import { useLoginStore } from "@/store/useLoginStore"
const store = useLoginStore();
console.log(store.count)
script>
Vue Router 的导航守卫中使用 store 的例子
import { createRouter } from 'vue-router'
import { useLoginStore } from "@/store/useLoginStore"
const router = createRouter({
// ...
})
// ❌ 由于引入顺序的问题,这将失败
// const store = useStore()
router.beforeEach((to) => {
// ✅ 这样做是可行的,因为路由器是在其被安装之后开始导航的,
// 而此时 Pinia 也已经被安装。
const store = useLoginStore();
if ( !store.user) return '/login'
})