前言#
都什麼年代了,還在用傳統狀態管理庫?快來學習Pinia
吧!
Pinia 這個名字的來源也很有意思,在西班牙語中,Pinia 是菠蘿這個詞最相似的英語發音,而一個菠蘿是由一組組單獨的花,結合在一起的,創造了多個水果,與 stores 類似,每一個都是獨立的,但最終都會有著聯繫
當我們點開vuex
的 github 庫的時候,會看見官方的提示Pinia is now the new default,而作為 Vue 的新一代的官方狀態管理庫,Pinia 有著更多優勢,解決了很多 Vuex 所留下的問題,在編寫的時候,會更有邏輯性,接下來就讓我們去試著了解一下吧!
Pinia 優點#
- vue3 vue2 均支持
- 拋棄了 Mutation 的操作,只有
state
、getter
和action
Actions
支持同步和異步- 支持使用插件擴展 Pinia 功能
- 不需要嵌套模塊,更加符合 Vue3 的
Composition api
- 支持 typescript
- 代碼更加的簡潔
用 Pinia 方式創建一個 store#
我們先快速創建一個空項目,再安裝Pinia
:
npm install pinia
雖然 Pinia 支持 vue2,但如果你使用的 vue 版本低於Vue2.7
, 還需獨立安裝 composition api: @vue/composition-api
(這裡建議直接升到 Vue2.7,相較於 Vue3 來說,跨度不會太大,但對 Vue 的生態支持的更好)
在main.ts
中對 Pinia 進行引入:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
接著,我們在src/store
中創建counter.ts
,並寫下基礎模板:
import { defineStore } from "pinia";
export const mainStore = defineStore('main', {
state: () => {
return {
helloWord: 'HelloWorld'
}
},
getters: {
},
actions: {
}
})
創建好倉庫mainStore(倉庫名)
後,我們將在組件中進行使用
<template>
<div class="">{{ store.helloWord }}</div>
</template>
<script lang="ts" setup>
import { mainStore } from "../store/counter";
const store = mainStore();
</script>
<style scoped></style>
當頁面顯示出helloWorld
後,則說明store
創建成功
Pinia 改變數據狀態#
我們在counter.ts
中的state
添加數據count
:
import { defineStore } from "pinia";
export const mainStore = defineStore('main', {
state: () => {
return {
count: 0,
helloWord: 'HelloWorld'
}
},
getters: {
},
actions: {
}
})
做一個擁有點擊事件
按鈕:
<template>
<div>
<button @click="handleClick">修改數據狀態</button>
</div>
</template>
<script setup lang="ts">
import { mainStore } from "@/stores/counter";
const store = mainStore();
const handleClick = () => {
store.count++;
};
</script>
將其引入App.vue
後,對數據狀態進行修改:
<template>
<Click />
<CountButton />
</template>
<script lang="ts" setup>
import Click from "./components/Click.vue";
import CountButton from "./components/CountButton.vue";
</script>
這時候便發現點擊按鈕後會改變count
的值了
在實際開發的過程中,我們往往需要多次調用store中的數據
,如果每次改變其值都需要{{store.****}}
未免太麻煩了,我們對其進行解構:
<template>
<div class="">{{ store.helloWord }}</div>
<div class="">{{ store.count }}</div>
<hr />
<!-- 解構後可以直接省略掉store,減少了代碼量 -->
<div>{{ helloWord }}</div>
<div>{{ count }}</div>
</template>
<script lang="ts" setup>
import { mainStore } from "../store/counter";
import { storeToRefs } from "pinia";
const store = mainStore();
// 進行解構
const { helloWord, count } = storeToRefs(store);
</script>
<style scoped></style>
注意,解構必須要用到storeToRefs()
函數!
PInia 修改數據的四種方法#
- 第一種方法:
const handleClick = () => {
store.count++;
};
- 第二種方式
$patch
const handleClickPatch=()=>{
store.$patch({
count:store.count+2
})
}
第二種方法寫起來雖然沒有第一種簡單,但更加適合於多個數據
的改變
- 第三種方式
$patch 傳遞函數
const handleClickMethod = () => {
// 這裡的state就是指代的倉庫中的state
store.$patch((state) => {
state.count++;
state.helloWord = state.helloWord === "jspang" ? "Hello World" : "jspang";
});
};
- 第四種方式
action
當業務邏輯很複雜的時候,就將方法寫在store
中的action
裡
actions: {
changeState() {
this.count++
this.helloWord = 'jspang'
}
}
Getters#
與 vuex 中的 getters 是差不多的,相當於 vue 中的計算方法。但當我們查看 vuex 文檔,會發現這個提示:
注意
從 Vue 3.0 開始,getter 的結果不再像計算屬性一樣會被緩存起來。這是一個已知的問題,將會在 3.1 版本中修復。詳情請看 PR #1878。
直到現在,這個提示仍然存在,這也是我更推薦使用Pinia
的原因之一。
Pinia 中的Getters
本身內部是可以進行緩存的,用代碼來驗證:
getters: {
phoneHidden(state) {
// 正則表達式
console.log('getters被調用');
return state.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
}
},
我們在組件中兩次調用 phone (數據),點開控制台進行查看
是只會出現一次的,證實了其具有緩存功能,這對於性能優化
是有好處的
Pinia 中的 stores 互相調用#
在實際開發中,往往不會只用到一個倉庫的,倉庫和倉庫之間通常會有一個調度,這裡我們再創建一個倉庫:
import { defineStore } from "pinia";
export const nameStore = defineStore('name', {
state: () => {
return {
list: ['小紅', '小美', '胖丫']
}
}
})
接著我們在counter.ts
中導入,import {jspangStore} from './jspang'
,在action
中進行調用:
actions: {
getList() {
console.log(nameStore().list);
}
}
查看控制台,發現已經成功調用了另一個倉庫的state
,成功做到了store
之間的相互調用
支持 VueDevtools#
Pinia 雖然作為一個新人,但已經全面支持 VueDevtools 了,這對於我們在實際項目開發中的調試有著很大的幫助,值得一提的是,在打開 VueDevtools 時,我們可以看見一個很俏皮的菠蘿的 logo
在開發時候的心情都更好了哈哈哈
注意:如果你使用的是 Pinia v2,請升級你的
Vue Devtools
到 v6 版本
Pinia 實戰:修改頭像#
說了這麼多,不如來實戰一下,在實際的項目中運用一下pinia
。開發背景:在我們製作網頁的時候,往往會涉及到註冊功能
,用戶頭像在登錄前
和登錄後
是不一樣的。如果只寫一個點擊事件來修改頭像的話,一旦刷新或是進行了頁面的跳轉便會恢復原樣,狀態無法得到保存,這時候我們就可以請出我們的 pinia 了
注意,為了 pinia 中的數據持久化存儲(存到 localstorage 或 sessionstorage 中),我們還需要安裝一個插件:pinia-plugin-persist,他能夠讓我們的操作更加方便,這裡便不展開贅述了,詳細可以參考官方文檔
1. 創建倉庫#
第一步還是先創建倉庫
(這裡默認你已經配置好了相關環境),創建store/user.ts
,具體代碼如下:
import { defineStore } from 'pinia';
export const mainStore = defineStore('main', {
state: () => {
return {
login: require('../assets/images/login.png')
}
},
// 開啟持久化
persist: {
enabled: true,
strategies: [
{ storage: localStorage, paths: ['login'] }
],
},
getters: {
},
actions: {
}
})
成功創建後,我們便將用戶頭像
存儲在了store
中,接下來便是將其在組件中使用
2. 組件中調用#
<!-- 頭像 -->
<a class="face" href="#/login">
<img :src="store.login" alt="" />
</a>
打開瀏覽器查看:
成功調用!接下來便是最重要的一步,實現登錄後頭像能夠順利存儲在本地。我們這時候選擇直接在actions
中添加修改數據的操作:
actions: {
changeHeadShot() {
console.log('數據存儲成功');
this.login = require('../assets/images/head.png')
}
}
將我們寫好的action
在組件中運用:
<template>
<!-- 省略不必要的代碼 (這裡用的是vant的組件)-->
<van-col span="8" @click="headerC">
<van-button class="btn2" plain hairlin type="primary" to="/">
<p class="text">登錄</p>
</van-button>
</van-col>
</template>
<script setup>
//引入store
import { mainStore } from '@/store/user'
const login = mainStore()
//實現點擊修改頭像的函數
function headerC() {
login.changeHeadShot()
}
</script>
<style scoped>
/* 省略不必要的代碼 */
</style>
接下來便是 "見證奇跡" 的時刻:我們點擊登錄按鈕後,頭像將會發生改變:
這時候無論我們怎麼刷新,頭像都不會出現變化了,我們打開控制台點開應用中的存儲
,可以看見我們的登錄頭像已經存儲在本地的瀏覽器中:
結語#
總而言之,無論你是否之前接觸過 Vuex,我都更推薦你使用Pinia
,他相較於 Vuex,有著更好的兼容性,在 Vuex 的基礎上去掉了Mutation
,讓語法更加簡練,更符合 Vue3 的Composition api
,Vuex 也不會再進行更新,現在已經處於維護狀態了,而 Pinia 作為下一代的 Vuex,又有什麼理由不去學習和使用呢?