Pinia 是 Vue.js 官方推荐的新一代状态管理库,它提供了非常简洁和直观的 API,可以极大地提高我们管理应用状态的效率。本文将深入介绍 Pinia 的各种高级用法,内容涵盖:
相比 Vuex,Pinia 有以下优点:
在 main.js 中导入 createPinia 并使用:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
可以通过 app.config.globalProperties.$pinia
访问 Pinia 实例。
与 Vue 的选项式 API 类似,我们也可以传入一个带有
state
、actions
与getters
属性的 Option 对象(废弃了Mutations)
使用 defineStore
API 定义 Store:
import { defineStore } from 'pinia'
export const useMainStore = defineStore('main', {
// state
state: () => {
return {
counter: 0
}
},
// getters
getters: {
doubleCount(state) {
return state.counter * 2
}
},
// actions
actions: {
increment() {
this.counter++
}
}
})
this
访问状态Pinia 提供多种选项配置 Store:
定义响应式状态:
state: () => {
return {
count: 0
}
}
必须是一个返回状态对象的函数。
定义 getter 计算属性:
getters: {
double(state) {
return state.count * 2
}
}
getter 可以接收参数:
getters: {
getMessage(state) {
return (name = 'Vue') => `Hello ${name}`
}
}
定义方法修改状态:
actions: {
increment(amount = 1) {
this.count += amount
}
}
actions 支持同步和异步操作。
配置数据持久化,需要使用插件:
persist: {
enabled: true,
strategies: [
{
key: 'my_store',
storage: localStorage,
},
]
}
通过 useXxxStore()
获取 Store 实例:
import { useMainStore } from '@/stores/main'
export default {
setup() {
const main = useMainStore()
main.increment()
}
}
读取状态、调用 actions 同 Vue 组件。
// 直接读取
const count = main.count
// 通过计算属性
const double = computed(() => main.doubleCount)
// 通过 storeToRefs
const { count } = storeToRefs(main)
main.increment()
const message = main.getMessage('Vue')
可以拆分多个 Store 管理不同模块状态:
stores
|- user.js
|- product.js
单独导出每个 Store 并在组件中使用:
import { useUserStore } from '@/stores/user'
import { useProductStore } from '@/stores/product'
export default {
setup() {
// ...
}
}
Store 可以互相引用:
// userStore.js
import { useProductStore } from './product'
export const useUserStore = defineStore({
// 可以直接使用 productStore
})
个人比较倾向这种语法
也存在另一种定义 store 的可用语法。与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
在 Setup Store 中:
ref()
就是 state
属性computed()
就是 getters
function()
就是 actions
Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。不过,请记住,使用组合式函数会让 SSR 变得更加复杂。
Pinia 默认状态不持久化,可以通过插件实现持久化:
npm install pinia-plugin-persistedstate
import persistedState from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persistedState)
在 Store 中配置 persist
:
export const useUserStore = defineStore({
persist: {
enabled: true
}
})
配置 storage
指定存储位置:
persist: {
storage: sessionStorage
}
Pinia 生态已有许多插件,可以扩展更多功能:
pinia-plugin-persistedstate
:数据持久化pinia-plugin-debounce
:防抖修改状态pinia-plugin-pinia-observable
:转换成 Observable使用插件:
import piniaPluginPersist from 'pinia-plugin-persist'
pinia.use(piniaPluginPersist)
Pinia支持Vue devtools
我们来通过一个购物车的例子看看 Pinia 的用法:
// store.js
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => {
return {
items: []
}
},
getters: {
total(state) {
return state.items.reduce((total, item) => {
return total + item.price
}, 0)
}
},
actions: {
addItem(item) {
this.items.push(item)
},
removeItem(id) {
this.items = this.items.filter(i => i.id !== id)
}
}
})
在组件中使用:
// Cart.vue
import { useCartStore } from '@/stores/cart'
setup() {
const cart = useCartStore()
return {
items: cart.items,
total: cart.total
}
}
可以看出代码非常简洁直观。
Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性。它接收一个可选参数,即 context。
export function myPiniaPlugin(context) {
context.pinia // 用 `createPinia()` 创建的 pinia。
context.app // 用 `createApp()` 创建的当前应用(仅 Vue 3)。
context.store // 该插件想扩展的 store
context.options // 定义传给 `defineStore()` 的 store 的可选对象。
// ...
}
然后用 pinia.use()
将这个函数传给 pinia
:
pinia.use(myPiniaPlugin)
插件只会应用于在 pinia
传递给应用后创建的 store,否则它们不会生效。
getStorage
函数:根据提供的 key
从本地存储中读取数据。如果数据无法解析或不存在,则返回 null
。setStorage
函数:将提供的值转换为 JSON 格式,并以指定的 key
保存到本地存储中。DEFAULT_KEY
常量:表示默认的本地存储键名前缀。如果在选项中未提供自定义键名,将使用该默认键名。Options
类型:定义了插件选项对象的类型,包含 key
(本地存储键名前缀)和 needKeepIds
(需要进行持久化的 Pinia 存储的 ID 数组)两个可选属性。import { PiniaPluginContext } from "pinia";
import { toRaw } from "vue";
// Get data from local storage by key
export function getStorage(key) {
const data = localStorage.getItem(key);
try {
return JSON.parse(data);
} catch (error) {
return null;
}
}
// Set data to local storage with a key
export function setStorage(key, value) {
const data = JSON.stringify(value);
localStorage.setItem(key, data);
}
const DEFAULT_KEY = "pinia";
type Options = {
key?: string;
needKeepIds?: string[];
};
const piniaPlugin = ({ key = DEFAULT_KEY, needKeepIds = [] }: Options) => {
return (context: PiniaPluginContext) => {
const { store } = context;
const data = getStorage(`${key}-${store.$id}`);
const subscribeToStore = () => {
if (needKeepIds.length === 0 || needKeepIds.includes(store.$id)) {
setStorage(`${key}-${store.$id}`, toRaw(store.$state));
}
};
store.$subscribe(subscribeToStore);
return {
...data,
};
};
};
export default piniaPlugin;