iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
Vue.js

這是我的 Vue.js 筆記,不知道有沒有機會幫到你系列 第 28

我的 Vue.js 筆記(28) - 認識大鳳梨

  • 分享至 

  • xImage
  •  

前言

我們已經知道元件與元件之間的溝通可以使用 propsemits,不過這東西就只能設定兩個元件溝通的狀態。

如果我們的元件樹的結構長得比較複雜一點,尾端的元件如果要將狀態傳回根元件,就必須一層一層又一層的傳遞狀態,單一一個狀態可能已經很麻煩了,要是各種狀態需要交互運作,那將會一場維護上的災難。

為此 Vue 提供了一個狀態管理工具:Vuex。這個狀態管理工具隨著版本的演進,更變了他的名稱叫做 Pinia,Logo 是一顆大鳳梨。

長得頗為可愛 XD

這篇文章預計要紀錄我們該怎麼在 Vue 中使用這顆大鳳梨

安裝大鳳梨

要使用大鳳梨,第一步就是先安裝他,一樣可以使用 npm 或是 yarn

npm install pinia
yarn install pinia

建立大鳳梨的實體

如同 createAppcreateRouter 那樣,我們也需要建構這顆大鳳梨的實體,建立實體的方法,是 'pinia' 引入的 createPinia 方法

import { createPinia } from "pinia";

建構實體與引用實體:

// 建立大鳳梨實體
const pinia = createPinia();

// 建立 Vue 實體
const app = createApp(App);
// 在 Vue 實體中使用大鳳梨
app.use(pinia);
app.mount("#app");

這個寫法也可以用 「串」 的:

createApp(App).use(pinia).mount("#app");

如果覺得建構實體、引用實體兩個行為要分開寫很麻煩,也可以直接把他們寫在一起:

const app = createApp(App);
app.use(createPinia());
app.mount("#app");

大鳳梨的狀態管理觀念

事實上 Pinia 語法跟 Vue 一樣,區分為 Option 與 Setup 兩種語法,筆者認為寫起來最優雅的會是 Setup 語法,而且他的寫法幾乎與 <script setup> 內容一樣,不用特別為了要使用 Pinia 而特別去記兩套語法。

不過由於 Pinia 官網中幾乎都是使用 Option 語法當範例,所以還是蠻有機會遇到跟著官網使用 Option 語法的團隊,為此我們可以用 Option 語法使用的專有名詞來對應到 Setup 語法中

接下來要使用的語法都會是 Setup 語法,不過帶到 Option 的專有名詞。

Store 儲存庫

要建立一個全域的狀態管理工具,需要有一個 Store 儲存庫,可以把他想像成一個倉庫,專門管理「某部分」的商業邏輯。

一個 Vue 專案的 Store 不一定只有一個,可以根據需求建立多個儲存庫,以達到好維護的狀態管理效果。

要定義一個儲存庫,一般我們會在 src 資料夾中新增一個 store 資料夾,並且建立一個預設資料夾 index.js

在 js 檔案中透過 pinia 引入 defineStore 方法:

import { defineStore } from "pinia";

我們要在這個方法中定義兩個參數,一個是 store 名稱,另一個是 callback。

第二個參數的內容,就是 Option Store 與 Setup Store 的差異所在,有興趣的讀者可以直接參考 Pinia 官網

export const useBookStore = defineStore("book", () => {
  // store 其他邏輯
});

store 第一個參數的名稱會被當成 ID,Pinia 會把他用來連結 devTools。

defineStore 會有一個回傳值,一樣可以任意命名,不過官方建議這個名稱以 use... 開頭為命名風格,且之後要使用這個 store,都會在程式中用這個命名引入。

State

State 狀態,就是對應到 Vue 的 ref()
其用法就跟之前在 Vue 裡面使用 ref() 一樣,該引入的要引入,該使用 .value 就使用。
唯一要注意的小地方是,在 Store 中使用的「變數」,記得都要 return 出來,之後才有辦法使用。

基本上,state 同時也代表著 store 的核心內容。

import { ref } from "vue";
export const useBookStore = defineStore("book", () => {
  const books = ref([
    { id: 1, title: "0 陷阱!0 誤解!8 天重新認識JavaScript!" },
    { id: 2, title: "重新認識Vue.js: 008 天絕對看不完的Vue.js 3.0 指南" },
    {
      id: 3,
      title:
        "D3.js資料視覺化實用攻略:完整掌握Web開發技術,繪製互動式圖表不求人",
    },
  ]);
  return { books };
});

Getter

既然 ref() 代表 State ,那麼 Getter 就是 computed() 了。

在 Pinia 中, Getter 完全等同於 State 的 computed()
如果看不懂什麼叫「State 的 computed()」的話,可以回頭想想 computed() 在 Vue 扮演的腳色,他是一個用來以狀態 (State) 產出狀態 (State) 的工具,後者會是前者的「計算值」,所以才會說「Getter 代表 State 的 computed()

在 Store 使用 Computed 的時候一樣,該引入的時候要引入,寫完之後記得要 return 出來。

import { ref, computed } from "vue";
export const useBookStore = defineStore("book", () => {
  const books = ref([
    { id: 1, title: "0 陷阱!0 誤解!8 天重新認識JavaScript!" },
    { id: 2, title: "重新認識Vue.js: 008 天絕對看不完的Vue.js 3.0 指南" },
    {
      id: 3,
      title:
        "D3.js資料視覺化實用攻略:完整掌握Web開發技術,繪製互動式圖表不求人",
    },
  ]);
  // getter
  const hasBook = computed(() => books.value.length > 0);
  // 記得要 return
  return { books, hasBook };
});

Action

有了 狀態,有了 計算值,當然也少不了「方法」。
Action 就是這個 method ,他的用法就跟 Vue 中使用 「方法」的方式一樣,定義一個函數就搞定了。

import { ref, computed } from "vue";
export const useBookStore = defineStore("book", () => {
  const books = ref([
    { id: 1, title: "0 陷阱!0 誤解!8 天重新認識JavaScript!" },
    { id: 2, title: "重新認識Vue.js: 008 天絕對看不完的Vue.js 3.0 指南" },
    {
      id: 3,
      title:
        "D3.js資料視覺化實用攻略:完整掌握Web開發技術,繪製互動式圖表不求人",
    },
  ]);
  const hasBook = computed(() => books.value.length > 0);

  // action
  const plusBook = () => {
    books.value.push({
      id: books.value.length + 1,
      title: "New Book",
    });
  };
  // action
  const deleteBook = () => {
    books.value.pop();
  };

  // 一樣記得要 return
  return { books, hasBook, plusBook, deleteBook };
});

使用 Store

要使用 Store ,在需要元件中引入當初對 Store 回傳值的命名 useXXXStore 即可直接使用:

import { useBookStore } from "./store";
const store = useBookStore();

接著就可以使用這個 store 定義的 stategetteraction

<button @click="store.plusBook">新增一本書</button>
<ul>
  <li v-for="book in store.books" :key="book.id">{{ book.title }}</li>
</ul>

<div>是否有書 : {{ store.hasBook ? "是" : "否"}}</div>

補充:在大鳳梨中使用 watch

我們也可以在 defineStore 的 callback 內容中,使用 watch 監聽 state 的變化,使用方式就跟一般的 watch 寫法一樣:

import { defineStore } from "pinia";
import { ref, computed, watch } from "vue";

export const useBookStore = defineStore("book", () => {
  const books = ref([
    { id: 1, title: "0 陷阱!0 誤解!8 天重新認識JavaScript!" },
    { id: 2, title: "重新認識Vue.js: 008 天絕對看不完的Vue.js 3.0 指南" },
    {
      id: 3,
      title:
        "D3.js資料視覺化實用攻略:完整掌握Web開發技術,繪製互動式圖表不求人",
    },
  ]);

  const hasBook = computed(() => books.value.length > 0);

  const plusBook = () => {
    books.value.push({
      id: books.value.length + 1,
      title: "New Book",
    });
  };

  const deleteBook = () => {
    books.value.pop();
  };

  // 在 Pinia 中使用 watch
  watch(
    books,
    (val) => {
      // 當書本資料發生變化時,將資料存入 localStorage
      localStorage.setItem("piniaState", JSON.stringify(val));
    },
    { deep: true }
  );

  return { books, hasBook, plusBook, deleteBook };
});

後記

這篇文章主要是在記錄 Pinia 的語法,範例程式的邏輯或許直接寫在 component 也可以達到一樣的效果。

不過使用 Pinia 的一個優點是,如果有多處元件都需要更改狀態的話,我們可以在大鳳梨中定義好「更改狀態的邏輯方法」,在需要的地方中引入方法後,即可達成這個需求,不用再單獨對各個元件定義 propsemits。畢竟寫 code 這種事情,多寫一個地方代表要多維護一個地方,能在單一位置處理好一套邏輯持續複用,才能讓我們的程式更優雅。

同時根據官方教學所說的,無論我們在開發大專案或是小專案,都很適合直接引入 Pinia 進行開發,畢竟他其實也沒什麼副作用。


上一篇
我的 Vue.js 筆記(27) - 使用 Vue Router
下一篇
我的 Vue.js 筆記(29) - 將專案打包上版到 GitHub Pages
系列文
這是我的 Vue.js 筆記,不知道有沒有機會幫到你30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言