---
# System prepended metadata

title: "\U0001F3C5 Pinia CDN 版起手教學文件"
tags: [Vue 3 套件教學]

---

---
tags: Vue 3 套件教學
---

# 🏅 Pinia CDN 版起手教學文件
## 為專案中加入 Pinia(CDN)

Pinia 在 CDN 與 Vite 上運作接近，本文件前段會以 CDN 為主，在 Vite 中會更加簡易。


### 步驟一：建立基本 Vue 環境

首先，先將基本的 Vue 環境建立起來，其中確保有多個元件，並且有各自的狀態。

- 卡片一：有狀態、按鈕
- 卡片二：只有版型，沒有狀態方法，稍後會讀取卡片一的狀態

```htmlembedded
<div id="app">
<card1></card1>
<card2></card2>
</div>

<script>
const {createApp} = Vue;
const card1 = {
  data() {
    return {
      num: 1
    }
  },
  template: `<div class="card">
    這是卡片一
    <input type="number" v-model="num"> {{num}}
    <button type="button">儲存資料</button>
  </div>`,
}

const card2 = {
  template: `<div class="card">
  </div>`,
}

const app = createApp({
  components: {
    card1,
    card2
  }
});

app.mount('#app');
</script>
```

### 步驟二：引入 Pinia CDN

因為 Pinia 在 CDN 環境中，需要相依 `vue-demi` 套件，所以必須額外載入此資源（Vite、CLI 則不需要手動加入）。 

```htmlembedded
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-demi/0.13.11/index.iife.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pinia/2.0.29/pinia.iife.js"></script>
```

### 步驟三：啟用 Pinia

將 Pinia 套用至當前環境中，其中個別方法所代表的意義

- defineStore：每一個儲存資料的空間都稱為 store，因此 definedStore 意思代表為建立儲存空間。
- createPinia：這是建立 Pinia 的初始化方法。
- mapState：Store 中會有多個狀態，這是取得狀態的函式
- mapActions：Store 中會有多個方法，這是取得方法的函式

```javascript
// 最前方將 defineStore、createPinia 方法取出
const { defineStore, createPinia, mapState, mapActions } = Pinia;


// 將 Pinia 套用至 Vue 環境中，請確保加入在 `const app = createApp...` 之後，並在 `app.mount` 之前。
const pinia = createPinia();
app.use(pinia);
```

### 步驟四：建立 Store

Store 可以做為多個元件中儲存資料的空間，可以新增一個檔案專門作為狀態管理使用。

其中的以下屬性分別類似於元件中的...
1. state -> data
2. actions -> methods
3. getters -> computed

```javascript
const cardStore = defineStore('card', {
  // state 概念同「data」，在此可以使用箭頭函式
  state: () => {
    return {
      number: 0,
    }
  },
  // actions 概念同「methods」
  actions: {
    // 可使用 this 使用 state 的資料內容
    updateNumber(num) {
      this.number = num;
    }
  },
  // getters 概念同「computed」
  getters: {
    // 解構資料來自於 state
    doubleNumber({number}) {
      return number * 2;
    }
  }
});
```

### 步驟五：將狀態、方法綁定回元件


#### 5-1：將資料寫入 Store

使用 mapActions 將方法取出，並綁定至元件元素上，記得要傳入參數喔

```javascript
const card1 = {
  data() {
    return {
      num: 1,
    }
  },
  template: `<div class="card">
    這是卡片一
    <input type="number" v-model="num"> {{num}}
    <button type="button" @click="updateNumber(num)">儲存資料</button>
  </div>`,
  methods: {
    // mapActions(store名稱, ['要取得的方法名稱'])
    ...mapActions(cardStore, ['updateNumber'])
  }
}
```


#### 5-2：將 store 資料取出

使用 mapState 將 state、getters 取出，並且渲染至畫面上

```javascript
const card2 = {
  template: `<div class="card">
    {{ number }} | {{ doubleNumber }}
  </div>`,
  computed: {
    // mapState(store名稱, ['要取得的值'])
    ...mapState(cardStore, ['number', 'doubleNumber'])
  }
}
```

接下來，回到畫面上操作 card1，資料寫入時 card2 也會連動呈現喔。

在 VueTools，也能看到另一個元件的資料狀態同步更新。

![](https://i.imgur.com/CxXstoT.png)


## 問題集

Q: 為什麼取得資料狀態叫做 mapState 而不是 mapGetters？
> 過去是叫做 mapGetters 沒錯，不過因為 mapState 可以取得 state 及 getters，所以就改名為 mapState。

Q: 那可以將 mapState 取得資料狀態在元件 data 上嗎？

> 不行

Q: 蛤～，那可以使用 mapState 雙向綁定資料在 v-model 上嗎？

> 誒...，是可以，但請思考這麼做的話，可能每次輸入都連動到多個元件重新渲染，你真的要這麼做ㄇ...
> (需要在 Vite 或 CLI 才能這樣運作)

Q: 我可以把全部東西都塞 Store 嗎？
> 原則上是可以，但要思考有些狀態還是適合存在各自的元件中
> 例如：還未送出的表單資訊，因為這是未確認的資料狀態，儲存在別的元件中有更好的效能，也能避免影響到其他元件。



## 完整範例程式碼

```htmlembedded
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue-demi/0.13.11/index.iife.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pinia/2.0.29/pinia.iife.js"></script>
  <style>
    .card {
      border: 1px solid black;
      padding: 5px;
    }
  </style>
</head>

<body>
  <div id="app">
    <card1></card1>
    <card2></card2>
  </div>

  <script type="module">
    const { createApp } = Vue;
    const { defineStore, createPinia, mapState, mapActions } = Pinia;

    // 建立 Store
    const cardStore = defineStore('card', {
      // state 概念同「data」，在此可以使用箭頭函式
      state: () => {
        return {
          number: 0,
        }
      },
      // actions 概念同「methods」
      actions: {
        // 可使用 this 使用 state 的資料內容
        updateNumber(num) {
          this.number = num;
        }
      },
      // getters 概念同「computed」
      getters: {
        // 解構資料來自於 state
        doubleNumber({number}) {
          return number * 2;
        }
      }
    })


    // 卡片一：將資料寫入至 Store
    const card1 = {
      data() {
        return {
          num: 1,
        }
      },
      template: `<div class="card">
        這是卡片一
        <input type="number" v-model="num"> {{num}}
        <button type="button" @click="updateNumber(num)">儲存資料</button>
      </div>`,
      methods: {
        // mapActions(store名稱, ['要取得的方法名稱'])
        ...mapActions(cardStore, ['updateNumber'])
      }
    }

    // 卡片二：將資料從 Store 中取出
    const card2 = {
      template: `<div class="card">
        {{ number }} | {{ doubleNumber }}
      </div>`,
      computed: {
        // mapState(store名稱, ['要取得的值'])
        ...mapState(cardStore, ['number', 'doubleNumber'])
      }
    }

    const app = createApp({
      components: {
        card1,
        card2
      },
    });

    const pinia = createPinia();
    app.use(pinia);

    app.mount('#app');
  </script>
</body>

</html>
```

