在《基于 vite 創建 vue3 專案》一文中整合了 pinia,有不少伙伴不知道 pinia 是什么,本文簡單介紹 pinia,主要包括三方面:
- pinia 的基本用法,在《基于 vite 創建 vue3 專案》中 demo 的基礎上簡單重構,
- 如何持久化 pinia 中的資料,保證瀏覽器重繪時,pinia 中的資料不丟失;
- 在 vue-router 路由守衛中如何使用 pinia,
文中的 demo 仍然基于 vite
1 pinia 的使用
1.1 pinia 是什么
在 vue 2.x 中,vuex 是官方的狀態管理庫,并且 vue 3 中也有對應的 vuex 版本,但 vue 作者尤大神看了 pinia 后,強勢推薦使用 pinia 作為狀態管理庫,下圖是 vue 官網 “生態系統”,pinia 是 vue 生態之一,
1.2 pinia 的特點
- 支持 vue2 和 vue3,兩者都可以使用 pinia;
- 語法簡潔,支持 vue3 中 setup 的寫法,不必像 vuex 那樣定義 state、mutations、actions、getters 等,可以按照 setup Composition API 的方式回傳狀態和改變狀態的方法,實作代碼的扁平化;
- 支持 vuex 中 state、actions、getters 形式的寫法,丟棄了 mutations,開發時候不用根據同步異步來決定使用 mutations 或 actions,pinia 中只有 actions;
- 對 TypeScript 支持非常友好,
1.3 pinia 的使用
在《基于 vite 創建 vue3 專案》中已經整合了 pinia,現簡單回顧并進行一些調整,
- 安裝 pinia 依賴:
yarn add pinia
- 創建 pinia 實體(根存盤 root store):
之前咱是在 main.ts 中創建的,現將其抽取到獨立的檔案中:
src/store/index.ts:
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
- 在 main.ts 中以插件的方式傳遞給 App 實體,
...
import pinia from '@/store'
...
app.use(pinia)
...
- 在 store/ 目錄下創建 modules 目錄,存盤每個模塊的狀態,將之前的 demo.ts 移動到 store/modules/ 中,這里使用最新的 Composition API setup 的方式來定義狀態,
src/store/modules/demo.ts:
import { defineStore } from 'pinia'
import { ref } from 'vue'
const useDemoStore = defineStore('demo', () => {
const counter = ref(0)
const increment = () => {
counter.value++
}
return {
counter,
increment
}
})
export default useDemoStore
- 在組件 about.vue 中使用 demo 中的狀態 counter 和改變狀態的函式 increment,代碼和之前一樣,
先引入 demo.ts 中定義的 useDemoStore 函式,通過該函式創建 demoStore 實體,然后就可以呼叫 demoStore 的狀態 counter 和 increment 函式了,這里需要注意,無論是 pinia 還是 vuex,通過解構的方式獲取狀態,會導致狀態失去回應性,如:
const { counter } = demoStore
此時的 counter 會丟失回應性,當其值改變時,其他組件不會監聽到,所以 pinia 提供了 storeToRefs 函式,使其解構出來的狀態仍然具備回應性,
const { counter } = storeToRefs(demoStore)
src/views/about.vue 完整代碼如下:
<template>
<div >
<h1>This is an about page</h1>
<h3>counter: {{counter}}</h3>
<el-button @click="add">
<el-icon-plus></el-icon-plus>
</el-button>
<div>
<svg-icon icon="http://www.yygnb.com/demo/car.svg"></svg-icon>
<svg-icon icon="car"></svg-icon>
<svg-icon class-name="icon" icon="http://www.yygnb.com/demo/car.svg"></svg-icon>
<svg-icon class-name="icon" icon="car"></svg-icon>
</div>
</div>
</template>
<script lang="ts" setup>
import useDemoStore from '@/store/modules/demo'
import { storeToRefs } from 'pinia'
import SvgIcon from '@/components/svg-icon/index.vue'
const demoStore = useDemoStore()
const { counter } = storeToRefs(demoStore)
const add = () => {
demoStore.increment()
}
</script>
<style scoped>
.icon {
color: cornflowerblue;
font-size: 30px;
}
</style>
最后在瀏覽器中訪問 about 頁面,可以正常運行,點擊加號按鈕,計數器會加1,
2 持久化 pinia 狀態
2.1 為什么需要持久化 pinia 狀態
在上面的 demo 中,假設計數器加到 5,如果重繪瀏覽器,counter 的值又會被初始化為 0,這是因為狀態是存盤在瀏覽器記憶體中的,重繪瀏覽器后,重新加載頁面時會重新初始化 vue、 pinia,而 pinia 中狀態的值僅在記憶體中存在,而重繪導致瀏覽器存盤中的資料沒了,所以 counter 的值就被初始化為 0,
在實際開發中,瀏覽器重繪時,有些資料希望是保存下來的,如用戶登錄后,用戶資訊會存盤在全域狀態中,如果不持久化狀態,那么每次重繪用戶都需要重新登錄了,
要解決這個問題非常簡單,在狀態改變時將其同步到瀏覽器的存盤中,如 cookie、localStorage、sessionStorage ,每次初始化狀態時從存盤中去獲取初始值即可,
說起來思路很簡單,可真正實作起來就各種問題了,所以咱們就使用 pinia 的插件 pinia-plugin-persistedstate 來實作,
2.2 pinia-plugin-persistedstate
接下來就使用 pinia-plugin-persistedstate 插件實作 pinia 狀態的持久化,
- 安裝依賴:
yarn add pinia-plugin-persistedstate
- 引入該插件,在創建 pinia 實體時傳入該插件
src/store/index.ts:
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
export default pinia
- 在需要持久化狀態的模塊中設定 persist,咱假設 demo 模塊需要對狀態需要持久化,defineStore 第一個引數定義唯一的模塊名,第二個引數傳遞 setup,其實還有第三個引數 options,在 options 中便可開啟 persist:
src/store/modules/demo.ts:
...
const useDemoStore = defineStore('demo', () => {
...
}, {
persist: true
})
此時改變 counter 的值后,重繪瀏覽器,counter 不會被重置為 0,仍然停留在重繪前的狀態,
persist 支持多種型別的值,最簡單的就是傳遞 true,此時會將狀態快取在 localStorage 中,該 localStorage 的 key 為模塊名(defineStore 的第一個引數),value 為該模塊的狀態物件,由于該模塊只有一個狀態 counter,故value為 {"counter":8},如下圖:

如果需要將其存盤在 sessionStorage 中,就需要設定 persist 的值為一個物件:
...
const useDemoStore = defineStore('demo', () => {
...
}, {
persist: {
key: 'aaa',
storage: sessionStorage
}
})
此時狀態就會同步快取到 sessionStorage 中,并且key 為咱們指定的 key:

persist 物件型別為 PersistedStateOptions,上面演示了 key 和 storage 屬性,該物件的其他屬性如下:
}
interface PersistedStateOptions {
/**
* Storage key to use.
* @default $store.id
*/
key?: string;
/**
* Where to store persisted state.
* @default localStorage
*/
storage?: StorageLike;
/**
* Dot-notation paths to partially save state. Saves everything if undefined.
* @default undefined
*/
paths?: Array<string>;
/**
* Customer serializer to serialize/deserialize state.
*/
serializer?: Serializer;
/**
* Hook called before state is hydrated from storage.
* @default null
*/
beforeRestore?: (context: PiniaPluginContext) => void;
/**
* Hook called after state is hydrated from storage.
* @default undefined
*/
afterRestore?: (context: PiniaPluginContext) => void;
}
3 在路由守衛中使用狀態
前面演示了在組件中使用 pinia,在組件外如何使用呢?這里演示在全域路由守衛中獲取狀態值,咱們創建一個路由守衛,在路由守衛中使用 nprogress 顯示頁面加載進度條,
3.1 創建全域路由守衛
- 安裝 nprogress
yarn add nprogress
yarn add @types/nprogress -D
- 創建全域路由守衛
src/router/guard/index.ts:
import router from '@/router'
import nProgress from 'nprogress'
import 'nprogress/nprogress.css'
nProgress.configure({
showSpinner: false
})
// 全域前置守衛
router.beforeEach((to, from) => {
nProgress.start()
return true
})
// 全域后置鉤子
router.afterEach(() => {
nProgress.done(true)
})
- 在 main.ts 中引入全域路由守衛:
...
import '@/router/guard/index'
...
此時路由切換時,頁面頂部會出現加載進度條,路由切換完成時該進度條消失,如果效果不明顯,可在前置守衛中 setTimeout 查看效果(個人覺得沒這必要,畫蛇添足):
// 全域前置守衛
router.beforeEach((to, from) => {
nProgress.start()
return new Promise(resolve => {
setTimeout(() => {
resolve(true)
}, 1000)
})
})
3.2 全域守衛中使用全域狀態
實際開發中,路由切換時,可能需要從全域狀態中獲取 token 等資訊,判斷是否能進入下一個頁面,這里演示路由切換時獲取 demo 中的 counter 的值,
首先試試在鉤子函式外面使用全域狀態:
...
import useDemoStore from '@/store/modules/demo'
import { storeToRefs } from 'pinia'
...
const demoStore = useDemoStore()
const { counter } = storeToRefs(demoStore)
// 全域前置守衛
router.beforeEach((to, from) => {
nProgress.start()
// 從 store 中獲取其他值,再決定回傳值
// 這里演示獲取 store 中 counter 的值
console.log(`counter:${counter}`)
return true
})
...
此時瀏覽器控制臺會報如下錯誤,這是因為 pinia 還沒有掛載到 app 上,

網上有些解決方案是直接實體化一個 pinia 實體,傳遞給 useDemoStore() 函式,如下:
...
import useDemoStore from '@/store/modules/demo'
import { storeToRefs } from 'pinia'
import pinia from '@/store'
...
const demoStore = useDemoStore(pinia)
const { counter } = storeToRefs(demoStore)
...
這樣做,瀏覽器控制臺不報錯了,頁面也可以正常加載,路由切換時,控制臺會輸出當前 counter 的值,
但是如果重繪瀏覽器,counter 的值又被初始化為 0,貌似前面設定的持久化插件 pinia-plugin-persistedstate 失效了,那應該怎么處理呢?
3.3 正確的處理方式
上面這種傳遞 pinia 物件給 useDemoStore() 函式只是一種野路子,pinia 官網已經清楚寫明組件外應該如何使用 pinia:
在鉤子函式外,pinia 還沒有被掛載,但是在前置守衛函式中,pinia 已經被掛載了,所以獲取全域狀態,需要在鉤子函式中進行,正確的實作如下:
import router from '@/router'
import nProgress from 'nprogress'
import 'nprogress/nprogress.css'
import useDemoStore from '@/store/modules/demo'
import { storeToRefs } from 'pinia'
nProgress.configure({
showSpinner: false
})
// 全域前置守衛
router.beforeEach((to, from) => {
nProgress.start()
const demoStore = useDemoStore()
const { counter } = storeToRefs(demoStore)
// 從 store 中獲取其他值,再決定回傳值
// 這里演示獲取 store 中 counter 的值
console.log(`counter:${counter.value}`)
return true
})
// 全域后置鉤子
router.afterEach(() => {
nProgress.done(true)
})
文中 demo 在 github 上搜索 vue3-vite-archetype,main 分支可以直接 yarn dev 啟動運行; template 分支是 yyg-cli 執行 yyg create 創建專案時拉取的模板,你也可以先執行 npm install -g yyg-cli 安裝 yyg-cli 腳手架工具,然后通過 yyg create xxx 創建專案,創建后的專案包含了 vue3 vite 的全部demo,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/513081.html
標籤:其他
