# Pinia使用
vue3官方推出新的状态管理器pinia来代替vuex,相比较vuex具有很多优点。
无需嵌套使用,更扁平化,再创建好pinia实例挂载到vue上后,就可以同时定义多个store,然后在各个组件中引入store就可以使用,各个store之间也可以相互引入使用。不像vuex那样还要通过模块引入,层层获取。
弃用mutation,直接使用state就可以读写数据
不在需要使用map辅助函数,直接导入函数就可以调用
更适用于组合式API方式使用
# 创建实例
安装:
yarn add pinia
# 或者使用 npm
npm install pinia
在src目录创建 stores
文件来创建pinia实例,来区分vuex的store,也更容易形容pinia可以同时创建多个store。
├── src
│ ├── stores
│ │ ├── index.ts # pinia实例
│ │ ├── modules # 模块
│ │ │ ├── app # app store
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ │
│ │ │ ├── public # public store
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
创建实例:
// stores/index.ts
import { createPinia } from 'pinia'
const store = createPinia()
export default store
引入:
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './stores'
const app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')
# 定义 Store
pinia定义多个store,这个多个store是什么意思呢,我们先来看看之前的vuex,使用vuex时我们只能创建一个store实例,如果要分模块,只能通过modules
来创建不同的模块或者通过命名空间来区分,引入和使用都是通过这个store实例,层层获取和调用,所以才会有computed和map辅助函数来帮助区分使用,显然这在多模块下使用很麻烦。
而在pinia中,把创建好的pinia实例挂载到vue实例上后我们就不需要关注这个实例了,我们只需要关心如何去创建多个store,pinia实例会自动帮我们把创建的store注册好,一个store就是一个vuex,不用在区分模块。各个store之间互不关联,使用哪个引入哪个,其数据是保持全局状态的。
定义store我们使用 defineStore()
来定义,其接收两个参数,第一参数是名字,第二个参数是定义该store的值,有两种定义方式setup函数式和选项式。
defineStore()
定义好的返回值名称最好使用store的名称,并且以 use
开头,以 Store
结尾,比如 useAppStore
、useUserStore
。
import { defineStore } from 'pinia'
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAppStore = defineStore('app', {
state: () => {
return {
theme: '',
userList: []
}
},
getters: {
userLength(state) {
return state.userList.length
}
},
actions: {
switchTheme(color) {
this.theme = color
}
}
})
# 两种定义方式
pinia的store有选项式和setup函数两种定义方式,选项式语义更像vuex好理解,setup函数式更像是组合式函数。使用哪种方式都行,前期可以使用选项式,倾向vuex写法,更容易理解。
选项式第二个参数为一个对象:
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0
}
},
getters: {
double: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
函数式第二个参数为一个函数,里面定义数据与方法,并在最后暴露出来:
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
# 使用Store
哪个组件使用就直接在哪个组件引入该store,然后通过返回值直接访问在 store 的 state、getters 和 actions 中定义的任何属性,不需像vuex那样麻烦。
store的返回值是一个reactive对象,不能直接进行解构state、getters,需要使用storeToRefs()
为其创建响应性,而action却可以随便解构。
<script setup>
import { storeToRefs } from 'pinia'
import { useAppStore } from '@/stores/modules/app'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useAppStore()
// 读写state
store.theme
store.theme = '#fff'
// 获取getters
store.userLength
// 调用actions
const switchDarkTheme = () => {
store.switchTheme('#000')
}
// 解构store
// 解构state、getters
const { theme, userLength } = storeToRefs(store)
theme.value
userLength.value
// 解构action
const { switchTheme } = store
switchTheme('#000')
</script>
<template>
<h1>{{store.theme}}</h1>
<a-button type="primary" @click="switchDarkTheme">深色主题</a-button>
</template>
# state
# 类型标注
创建store时我们最好创建一个types文件为其进行TS类型标注。
// app/index.ts
import { defineStore } from 'pinia'
import type { AppState } from './types'
export const useAppStore = defineStore('app', {
state: (): AppState => ({
theme: '',
userList: []
}),
getters: {
userLength(state): number {
return state.userList.length
}
},
actions: {
switchTheme(color: string) {
this.theme = color
}
}
})
types类型标注文件:
// app/types.ts
// app store 接口
interface UserInfo {
id: number,
name: string
}
export interface AppState {
theme: string,
userList: UserInfo[]
}
# 访问和修改
pinia去除mutations,获取和修改state的值就直接用state就行了。
<script setup>
import { useAppStore } from '@/stores/modules/app'
const store = useAppStore()
// 获取整个state
store.$state
// 获取某个值
store.theme
// 修改某个值
store.theme = '#fff'
</script>
<template>
<h1>{{store.theme}}</h1>
</template>
# 重置、批量修改state
将 state 所有的值重置为初始值可以使用$reset()
方法
<script setup>
import { useAppStore } from '@/stores/modules/app'
const store = useAppStore()
store.$reset()
</script>
同时修改 state 中的多个值或者添加值使用 $patch
方法
<script setup>
import { useAppStore } from '@/stores/modules/app'
const store = useAppStore()
// 批量修改
store.$patch({
// 修改state值
theme: '#fff',
// 新增state
age: 120,
name: 'DIO',
})
// 通过函数修改复杂类型数据
store.$patch((state) => {
state.userList.push({ name: 'shoes', id: 1 })
state.theme = 'blue'
})
</script>
# 订阅state
订阅 state 通过 $subscribe()
方法监听state的变化。使用watch也能进行监听state,但$subscribe()
方法只触发一次
<script setup>
import { useAppStore } from '@/stores/modules/app'
const store = useAppStore()
// 任何修改和变更都会被监听到
store.$subscribe((mutation, state) => {
// mutation变更事件
console.log(mutation.type) // 变更类型
console.log(mutation.storeId ) // store名称
console.log(mutation.payload) // 传递给 cartStore.$patch() 的补丁对象
// 变更后的state
console.log(state)
})
</script>
监听整个pinia实例变化
<script setup>
import { watch } from 'vue'
import pinia from '@/stores'
watch(
pinia.state,
(state) => {
console.log(state)
},
{ deep: true }
)
</script>
# getters
getters 与 vuex 的 Getter 一样是state的计算属性。在getters中既可以其他getters也可以访问其他store的getters。
export const useCountStore = defineStore('count', {
state: () => ({
count: 0,
}),
getters: {
// 普通写法
floorCount(state) {
return state.count--
},
// 箭头函数写法
doubleCount: (state) => state.count * 2
}
})
获取getters
<script setup>
const store = useCounterStore()
store.count = 3
store.doubleCount
</script>
# 向getters传参
通过返回一个函数来向getters传递参数
export const useAppStore = defineStore('app', {
state: () => ({
userList: [],
}),
getters: {
getUserById: (state) => {
return (userId) => state.userList.find((item) => item.id === userId)
}
}
})
在组件中使用:
<script setup>
import { storeToRefs } from 'pinia'
import { useAppStore } from '@/stores/modules/app'
const store = useAppStore()
// 解构store
const { getUserById } = storeToRefs(store)
// 需要getUserById.value(2)
const { getUserById } = storeToRefs(store)
const getUser = getUserById.value(2)
console.log(getUser)
</script>
# 访问其他getter
在getters中可以使用 this 访问其他getters
export const useCountStore = defineStore('count', {
state: () => ({
count: 0,
}),
getters: {
// 类型是自动推断出来的,因为我们没有使用 `this`
doubleCount: (state) => state.count * 2,
// 需要手动推断返回类型
doubleCountPlusOne(): number {
return this.doubleCount + 1
}
}
})
# 访问其他store的getters
访问其他store的getters直接在getter中引入就行
import { useOtherStore } from './other-store'
export const useCountStore = defineStore('count', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
}
}
})
# actions
actions类似组件中的method方法,可以是异步的。action中可以通过 this
访问整个store实例。
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: ''
}),
getters: {
doubleCount: (state) => state.count * 2
}
actions: {
setName() {
this.name = '标题'
},
increment() {
this.count++
},
setCount(count) {
this.count = count
},
setDoubleCount(count) {
this.count = this.doubleCount + count
}
}
})
调用actions
<script setup>
const store = useCounterStore()
store.increment()
store.setCount(100)
store.setDoubleCount(101)
// 或解构调用
const { increment, setCount, setDoubleCount } = store
increment()
setCount(100)
setDoubleCount(101)
</script>
# 访问其他action
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: ''
}),
actions: {
setName() {
this.name = '标题'
},
increment() {
this.setName()
this.count++
}
}
})
# 访问其他store的actions
访问其他store的actions直接在action中引入就行
import { useOtherStore } from './other-store'
export const useCountStore = defineStore('count', {
state: () => ({
// ...
}),
actions: {
setName() {
this.name = '标题'
},
increment() {
const otherStore = useOtherStore()
if (otherStore.getAuthor()) {
this.count++
}
}
}
})
# 订阅action
订阅 action 通过 $onAction()
方法监听action的变化和结果。
<script setup>
import { useAppStore } from '@/stores/modules/app'
const store = useAppStore()
const unsubscribe = store.$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()
</script>
# 在组件外使用 store
在组件外使用 store 必须先确保pinia实例挂载到vue实例上之后才行,比如使用router导航守卫监听路由,就先要确定pinia是否挂载。
import { createRouter } from 'vue-router'
const router = createRouter({
// ...
})
// ❌ 由于引入顺序的问题,这将失败
const store = useStore()
router.beforeEach((to, from, next) => {
// 我们想要在这里使用 store
if (store.isLoggedIn) next()
else next('/login')
})
router.beforeEach((to) => {
// ✅ 这样做是可行的,因为路由器是在其被安装之后开始导航的,
// 而此时 Pinia 也已经被安装。
const store = useStore()
if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})