文章大綱
還記得我們在vue2 的時代,最紅的狀態管理套件是 vuex;隨著 vue3 的發展,許多的套件也隨大家撰寫 Composition API 的使用習慣跟著做相應的調整。
什麼是 pinia?
pinia 就是針對 Composition API 重新設計的 Vue 跨元件/頁面共享狀態管理套件。
為什麼要安裝 pinia?
當然 vue3 的 Provide
和 Inject
等 API 已經有方法可以符合跨元件的資料傳遞,但是一但你的專案大起來,pinia 可以提供一種更結構化和模組化的方式來管理應用程式的狀態。
這個所謂的結構化、模組化真的就是要自己寫過一遍之後,好好品味一番了。
詳細說明可以參考我之前寫的文章,只是之前是用 vuex 來做比較。
你品,你細品。
安裝 pinia
安裝的步驟都可以在官網找到
$ yarn add pinia
安裝完成後,你需要到main.js
註冊套件,
一開始你如果是剛剛建立的vue專案,你的main.js
會長這樣:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
那我們註冊 pinia 之後會變成這樣:
import { createApp } from 'vue'
import { createPinia } from 'pinia';
import App from './App.vue'
const app = createApp(App);
app.use(createPinia());
app.mount('#app');
註冊完成,我們要來定義 store。
什麼是 store?
store 就是 pinia 建立實例的檔案,取名為 store。
例如我們一個專案中會有很多種類型的資料要管理,像是訂單資料
、會員資料
、使用者登入資訊
…等。
我們可以挨個為他們建立單獨的 store 檔案,像是 useOrderStore
、useMemberStore
、useUserStore
。
所以一個專案中通常會有很多個 store 檔案。
store 裡面會有哪些東西?
pinia 在定義 store 裡面的寫法有分兩種:Setup function store 、 Options object store,本篇文章採用的是 Options object store。
但是 pinia 也有提到,其實 setup store 比 option store 具有更多的靈活性,但是如果你不確定你要選用哪一個的話,就先用 option store。
store 裡面會有三個名詞:state、getters、actions。
這三個資料會被當作 option 傳入定義 store 的方法中(下一步會說明)
state
:存放資料的地方getters
:存放計算過的 state 的地方actions
:存放修改 state 的方法
而學過 vue2 的人,你可以把他理解為
- state 就是 data
- getters 就是 computed
- actions 就是 methods
如何撰寫 store 檔案?
在/src
路徑下新增一個資料夾 stores
然後我們在 stores
資料夾中新增一隻store檔案 user.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
//...
}),
getters: {
//...
},
actions: {
//...
},
});
defineStore
用來創建store的方法。- 第一個參數要傳入一個唯一的store名稱,在上面我們是傳
user
,那之後你要創建其他的store的話,就不能再用到user
這個名稱。 - 第二個參數就要放我們要儲存的資料囉!
題外話,如果你之後是建立多個 store,他會長得類似這樣:
import { defineStore } from 'pinia';
export const useOrderStore = defineStore('order', {
state: () => ({
//...
}),
getters: {
//...
},
actions: {
//...
},
});
import { defineStore } from 'pinia';
export const useMemberStore = defineStore('member', {
state: () => ({
//...
}),
getters: {
//...
},
actions: {
//...
},
});
資料夾結構:
--stores
|--user.js
|--order.js
|--member.js
State
存放資料的地方,可以存放各種類型:文字、布林、數字、陣列、物件。
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
username: 'jess0099',
role: 1,
notificationOpen: false,
permissions: [],
userInfo: {
name: 'Jessica',
email: 'jessica@penueling.com',
token: 'wejfoe7336ewqgEfWw236647DSDvwqovqjwefQWGO@39eg2ujof'
}
}),
getters: {
//...
},
actions: {
//...
},
});
讀取
我們可以在的.vue
檔案中讀取 state 的內容
<script setup>
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
</script>
<template>
<div>my name is {{ userStore.userInfo.name }}</div>
</template>
這樣就能在畫面上看到「my name is Jessica」的字樣囉!
即時數據
這邊要注意的一點是,我們如果直接把 useUserStore 解構,他的資料會變得無法即時更新。
const { userInfo } = useUserStore();
// ❌ 這樣讀取state,他是不會隨著資料變動而讀到最新的資料的。
如果你在畫面中非解構不可,請使用storeToRefs
方法
<script setup>
import { storeToRefs } from "pinia";
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
const { userInfo } = storeToRefs(userStore);
// ✅ 這樣讀取state,即使其他的頁面或組件有更新資料,你也可以取得最新的資料喔
</script>
<template>
<div>my name is {{ userInfo.name }}</div>
</template>
修改
除了讀取資料以外,你也可以用 useUserStore 直接修改資料
const userStore = useUserStore();
const handleClick = () => {
userStore.username = "sss";
};
// ✅ 這樣修改state,資料可以成功及時修改
但是如果你是解構出來的 state ,是不能直接修改的喔!
const userStore = useUserStore();
const { username } = storeToRefs(userStore);
const handleClick = () => {
username = "sss";
};
// ❌ 這樣修改state,會直接報錯
修改多個資料
假設你今天要修改的是 user store 中的很多個 state,我們可以使用$patch
方法,
原本你可能會這樣寫:
const userStore = useUserStore();
const handleClick = () => {
userStore.username = "sss";
userStore.role = 2;
userStore.notificationOpen = true;
};
使用$patch
之後可以變成這樣:
const userStore = useUserStore();
const handleClick = () => {
userStore.$patch({
username: "sss",
role: 2,
notificationOpen: true
});
};
如果你的 state 是深層資料,像是陣列:
const userStore = useUserStore();
const handleClick = () => {
userStore.$patch((state) => state.permissions.push(1));
};
重置
假設你需要將整個 user store 的資料都回復成你當初 stores/user.js
檔案裡的初始狀態,可以使用 $reset
方法
const userStore = useUserStore();
const handleReset = () => {
userStore.$reset();
};
監聽
當 state 發生改變的時候,我們可以用$subscribe
來捕捉事件
<script setup>
const userStore = useUserStore();
userStore.$subscribe((mutation, state) => {
console.log(mutation.payload) //使用patch時傳入的參數
console.log( state); //變化後的state
});
</script>
用 vue3 自己的 watch
也是可以的喔!只是寫法就會稍微冗長一點~
<script setup>
import { watch } from "vue";
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
const { username } = storeToRefs(userStore);
watch(
username,
(state) => {
console.log(state);
},
{ deep: true }
);
getters
存放計算過後的 state,宣告的時候可以使用箭頭韓式,回傳的參數就是當下的 state
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
state: () => ({
userInfo: {
token: "wejfoe7336ewqgEfWw236647DSDvwqovqjwefQWGO@39eg2ujof",
},
}),
getters: {
getToken: (state) => state.userInfo.token,
isAuth: () => this.getToken !== null,
},
actions: {
//...
},
});
- 如果是要讀取
state
來計算,箭頭函式返回的參數可以讀取 state。 - 如果是要讀取其他的
getters
來計算 ,就可以使用this
來讀取。
讀取
我們可以在的.vue
檔案中讀取 getters 的內容
const userStore = useUserStore();
const { isAuth } = storeToRefs(userStore);
<div v-if="isAuth">妳好,</div>
<button v-else>登入</button>
你也可以使用在 vue-router 裏面
import { createRouter, createWebHistory } from 'vue-router';
import { useUserStore } from '@/stores/user';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
//...
],
});
router.beforeEach((to, form, next) => {
const store = useUserStore();
if (to.name !== 'Login' && !store.isAuth) next({ name: 'Login' });
if (to.name === 'Login' && store.isAuth) next({ name: 'Home' });
else next();
});
export default router;
讀取動態資料
如果你要讀取的資料是動態的,你也可以傳入參數去讀取
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
state: () => ({
userList: [
{
id: 1,
name: "Jessica",
email: "jessica@penueling.com",
token: "wejfoe7336ewqgEfWw236647DSDvwqovqjwefQWGO@39eg2ujof",
},
{
id: 2,
name: "Anna",
email: "anna@penueling.com",
token: "wedf5HS8E2VD76ewqwefQWGO@39eg2ujof",
},
],
}),
getters: {
getUserById: (state) => (userId) =>
state.userList.find((i) => i.id === userId),
},
});
在頁面中查看
const userStore = useUserStore();
const { getUserById } = storeToRefs(userStore);
<template>
<div>{{ getUserById(2)?.name }}</div>
</template>
讀取其他store
想要使用另一個 store 的 getter 的話,那就直接在 getter 內使用就好
import { useOtherStore } from './other-store'
export const useStore = defineStore('user', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
發表評論
想要加入討論嗎?請盡情發表您的想法!