安装
npm install pinia -S
main.js引入
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
创建 Store
创建 /src/store/user.js 文件
1. 通过选项式创建
defineStore
- 函数第一个参数是唯一ID(名字)
- 第二个参数是一个对象
- state 是 store 的数据 (data)
- getters 是 store 的计算属性 (computed)
- actions 是方法 (methods)
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
state: () => {
return {
userInfo: null,
};
},
getters: {},
actions: {},
})
2. 通过组合式API创建
- 第二个参数是一个函数
- ref() 是 state 属性
- computed() 是getters
- function() 是 actions
export const useUserStore = defineStore('user', () => {
const user = ref(null)
function getUserName() {
user.value.userName
}
return { user, getUserName }
})
通过两种方式都可以创建一个store,你不需要纠结用哪种,你喜欢就行
使用state
在vue文件中使用
<template>
<div>
{{ userStore.userInfo }}
</div>
</template>
<script setup>
import { useUserStore } from '@/store/user'
let userStore = useUserStore()
console.log(userStore.userInfo)
</script>
使用storeToRefs函数解构
<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store/user'
let userStore = useUserStore()
const { userInfo } = userStore // 破坏了响应性, userName始终不会变
// 可以这样写,他是响应式的
const userInfo = computed(() => userStore.userInfo)
// 或者使用storeToRefs函数解构
const { userInfo } = storeToRefs(userStore) // 这样是响应式的
</script>
变更 state
重置 state
通过调用 store 的 $reset() 方法将 state 重置为初始值。
const store = useStore()
store.$reset()
直接修改
<script setup>
import { useUserStore } from '@/store/user'
let userStore = useUserStore()
userStore.userInfo = { userName: '张三' }
userStore.userInfo.userName = '李四'
</script>
通过 $patch 修改
// 通过$patch可以修改一个或多个state
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
// 也许你想对数组进行增删改,那么可以使用函数的形式
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
通过actions
import { useUserStore } from '@/store/user'
let userStore = useUserStore()
userStore.setUserInfo({ userName: '张三' })
替换 state
你不能完全替换掉 store 的 state,因为那样会破坏其响应性。但是,你可以 patch 它
// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })
你也可以通过变更 pinia 实例的 state 来设置整个应用的初始 state。这常用于 SSR 中的激活过程
pinia.state.value = {}
订阅 state
类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次 (例如,当使用上面的函数版本时)
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'direct' | 'patch object' | 'patch function'
// 和 cartStore.$id 一样
mutation.storeId // 'cart'
// 只有 mutation.type === 'patch object'的情况下才可用
mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('cart', JSON.stringify(state))
})
默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离
<script setup>
const someStore = useSomeStore()
// 此订阅器即便在组件卸载之后仍会被保留
someStore.$subscribe(callback, { detached: true })
</script>
TIP:你可以在 pinia 实例上使用 watch() 函数侦听整个 state
watch(
pinia.state,
(state) => {
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
getters
大多数时候,getter 仅依赖 state,不过,有时它们也可能会使用其他 getter。因此,即使在使用常规函数定义 getter 时,我们也可以通过
this 访问到整个 store 实例
export const useStore = defineStore('main', {
state: () => ({
count: 0,
}),
getters: {
// 自动推断出返回类型是一个 number
doubleCount(state) {
return state.count * 2
},
// 返回类型**必须**明确设置
doublePlusOne(): number {
// 整个 store 的 自动补全和类型标注 ✨
return this.doubleCount + 1
},
},
})
直接访问 store 实例上的 getter
<script setup>
import { useCounterStore } from './counterStore'
const store = useCounterStore()
</script>
<template>
<p>Double count is {{ store.doubleCount }}</p>
</template>
调用其他store中的getter
想要使用另一个 store 的 getter 的话,那就直接在 getter 内使用就好
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
向 getter 传递参数
Getter 只是幕后的计算属性,所以不可以向它们传递任何参数。不过,你可以从 getter 返回一个函数,该函数可以接受任意参数
export const useStore = defineStore('main', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
在组件中使用
<script setup>
import { useUserListStore } from './store'
const userList = useUserListStore()
const { getUserById } = storeToRefs(userList)
// 请注意,你需要使用 `getUserById.value` 来访问 <script setup> 中的函数
</script>
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>
请注意这样做 getter 将不再被缓存,它们只是一个被你调用的函数。不过,你可以在 getter 本身中缓存一些结果,虽然这种做法并不常见,但有证明表明它的性能会更好:
export const useStore = defineStore('main', {
getters: {
getActiveUserById(state) {
const activeUsers = state.users.filter((user) => user.active)
return (userId) => activeUsers.find((user) => user.id === userId)
},
},
})
actions
类似 getter,action 也可通过 this 访问整个 store 实例,并支持完整的类型标注(以及自动补全✨)。不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action
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)
// 让表单组件显示错误
return error
}
},
},
})
Action 可以像函数或者通常意义上的方法一样被调用
<script setup>
const store = useCounterStore()
// 将 action 作为 store 的方法进行调用
store.randomizeCounter()
</script>
<template>
<!-- 即使在模板中也可以 -->
<button @click="store.randomizeCounter()">Randomize</button>
</template>
调用其他store中的action
想要使用另一个 store 的话,那你直接在 action 中调用就好了
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')
}
},
},
})
订阅 action
你可以通过 store.$onAction() 来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after 表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。同样地,onError 允许你在 action 抛出错误或 reject 时执行一个回调函数。这些函数对于追踪运行时错误非常有用,类似于Vue docs 中的这个提示。
这里有一个例子,在运行 action 之前以及 action resolve/reject 之后打印日志记录
const unsubscribe = someStore.$onAction(
({
name, // action 名称
store, // store 实例,类似 `someStore`
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()
默认情况下,action 订阅器会被绑定到添加它们的组件上(如果 store 在组件的 setup() 内)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 true 作为第二个参数传递给 action 订阅器,以便将其从当前组件中分离
<script setup>
const someStore = useSomeStore()
// 此订阅器即便在组件卸载之后仍会被保留
someStore.$onAction(callback, true)
</script>