--- tags: Vue 3 Pinia 快速上手 --- ## 為專案中加入 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.14.6/index.iife.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pinia/2.1.7/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> ```