還記得我們在vue2 的時代,最紅的狀態管理套件是 vuex;隨著 vue3 的發展,許多的套件也隨大家撰寫 Composition API 的使用習慣跟著做相應的調整。

什麼是 pinia?

pinia 就是針對 Composition API 重新設計的 Vue 跨元件/頁面共享狀態管理套件。

為什麼要安裝 pinia?

當然 vue3 的 ProvideInject 等 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 檔案,像是 useOrderStoreuseMemberStoreuseUserStore

所以一個專案中通常會有很多個 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 的方法中(下一步會說明)

  1. state:存放資料的地方
  2. getters:存放計算過的 state 的地方
  3. actions:存放修改 state 的方法

而學過 vue2 的人,你可以把他理解為

  1. state 就是 data
  2. getters 就是 computed
  3. actions 就是 methods

如何撰寫 store 檔案?

/src路徑下新增一個資料夾 stores

然後我們在 stores 資料夾中新增一隻store檔案 user.js

import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    //...
  }),
  getters: {
    //...
  },
  actions: {
    //...
  },
});

  1. defineStore 用來創建store的方法。
  2. 第一個參數要傳入一個唯一的store名稱,在上面我們是傳user,那之後你要創建其他的store的話,就不能再用到user這個名稱。
  3. 第二個參數就要放我們要儲存的資料囉!

題外話,如果你之後是建立多個 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: {
    //...
  },
});
  1. 如果是要讀取 state 來計算,箭頭函式返回的參數可以讀取 state。
  2. 如果是要讀取其他的 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
     },
   },
 })

Actions

0
0 回復

發表評論

想要加入討論嗎?
請盡情發表您的想法!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。