Pinia
安装
npm install pinia
引入
创建一个 pinia 实例 (根 store) 并将其传递给应用:
import { createApp } from 'vue'
import { createPinia } from 'pinia' //[!code hl]
import App from './App.vue'
const pinia = createPinia() //[!code hl]
const app = createApp(App)
app.use(pinia) //[!code hl]
app.mount('#app')
定义 Store
defineStore()
defineStore()
的返回值可以任意命名,但最好使用 store 的名字,同时以 use
开头且以 Store
结尾,如 useUserStore
,useCartStore
,useProductStore
。
import { defineStore } from 'pinia'
// 第一个参数是你的应用中 Store 的唯一 ID
export const useAlertsStore = defineStore('alerts', {
// 第二个参数可接受两类值:Setup 函数或 Option 对象
})
API 风格
Option Store
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
getters: {
double: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
Setup Store
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
// state
const count = ref(0)
// getters
const double = computed(() => {
count * 2
})
// actions
function increment() {
count.value++
}
return { count, double, increment }
})
TIP
state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。
三个概念
State
在 Pinia 中,state 被定义为一个返回初始状态的函数。
- 访问 state
const store = useStore()
store.count++
- 重置 state
const store = useStore()
store.$reset()
组合式 API 下重置 state
解决直接调用 store.$reset
时报错: Store "xxx" is built using the setup syntax and does not implement $reset()
。
const pinia createPinia();
pinia.use(({store}) => {
const initialState = JSON.parse(JSON.stringify(store.$state));
store.$reset = () => {
store.$state = JSON.parse(JSON.stringify(initialState));
}
})
app.use(pinia)
- 变更 state
// 对象
store.$patch({
count: store.count + 1
})
// 函数
store.$patch((state) => {
state.count += 1
})
- 替换 state
// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })
替换 store 中的整个 state 对象
pinia.state.value = {}
- 订阅 state
store.$subscribe((mutation, state) => {
console.log(mutation, state)
})
TIP
默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。即当该组件被卸载时,将被自动删除。如果想在组件卸载后依旧保留它们,可将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离。
Getter
推荐使用箭头函数,并且它将接收 state 作为第一个参数:
export const useStore = defineStore('main', () => {
const count = ref(0)
const doubleCount = computed(() => count * 2)
return { count, doubleCount }
})
- 访问其他 getter
export const useStore = defineStore('main', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const doubleCountPlusOne = computed(() => doubleCount.value + 1)
return { count, doubleCount, doubleCountPlusOne }
})
- 访问其他 store 的 getter
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', () => {
const localData = ref(0)
const otherGetter = computed(() => {
const otherStore = useOtherStore()
return localData.value + otherStore.data
})
return { otherGetter }
})
Action
export const useCounterStore = defineStore('main', () => {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
})
- 访问其他的 action
import { defineStore } from 'pinia'
export const useStore = defineStore('main', () => {
const count = ref(0)
const increment = () => doubleIncrement()
const doubleIncrement = () => (count.value += 2)
return { count, increment, doubleIncrement }
})
- 访问其他 store 的 action
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', () => ({
const count = ref(0);
const increment = () => count.value++;
const otherAction = () => {
const otherStore = useOtherStore();
return increment() + otherStore.data
}
return { count, increment, otherAction}
}));
- async & await
import { defineStore } from 'pinia'
export const useStore = defineStore('main', () => {
const count = ref(0)
const fetchData = async (api) => {
const response = await fetch(api)
const data = await response.json()
console.log(data)
}
return { count, fetchData }
})
- 订阅 action
store.$onAction((context, payload) => {
// 在 action 执行前执行的逻辑
console.log('Action 执行前')
// 可以对 payload 进行操作或根据需求执行其他逻辑
// 在 action 执行后执行的逻辑
context.$nextTick(() => {
console.log('Action 执行后')
})
}, true)
使用 Store
store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value
。
<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量
const store = useCounterStore()
console.log(store.count, store.double, store.increment)
</script>
响应式解构
为了从 store 中提取 state 时保持其响应性,你需要使用 storeToRefs()。它将为每一个响应式数据创建引用。当你只使用 state 而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
/**
* `count` 和 `doubleCount` 是响应式的 ref
* 同时通过插件添加的属性也会被提取为 ref
* 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
*/
const { count, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>
组合式 Store
如果两个或更多的 store 相互使用,它们不可以通过 getters 或 actions 创建一个无限循环。它们也不可以同时在它们的 setup 函数中直接互相读取对方的 state。
Pinia 插件
Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性且具体响应式。它接收一个可选参数,即 context。通常用于添加全局对象很有用,如路由器、modal 或 toast 管理器。
function myPiniaPlugin(context) {
context.pinia // 用 `createPinia()` 创建的 pinia。
context.app // 用 `createApp()` 创建的当前应用(仅 Vue 3)。
context.store // 该插件想扩展的 store
context.options // 定义传给 `defineStore()` 的 store 的可选对象。
}
扩展 Store
//方法一
const myPiniaPlugin = () => {
return {
secret: 'the cake is a lie',
handler: () => 'handler'
}
}
//方法二: 解构赋值 context.store
const myPiniaPlugin = ({ store }) => {
;(store.secret = 'the cake is a lie'), (store.handler = () => 'handler')
}
安装插件
import { createPinia } from 'pinia'
const pinia = createPinia()
pinia.use(myPiniaPlugin)
使用插件
import useStore from './store'
const store = useStore()
console.log(store.secret, store.handler)
第三方插件
pinia-plugin-persistedstate (持久化存储)
- 安装
npm i pinia-plugin-persistedstate
- 引入
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
- 定义
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
const someState = ref('hello pinia')
return { someState }
},{
persist: true,
})
配置 SessionStorage
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
const someState = ref('hello pinia')
return { someState }
},{
persist: {
storage: sessionStorage,
paths: ['someState'],
}
})
Pinia 与 Vue Router 导航守卫
解决错误:"getActivePinia()" was called but there was no active Pinia. Did you forget to install 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'
})