[Vue] 新一代狀態管理工具 Pinia
在閱讀這篇文章之前,希望你已經具備 Vue 的相關知識以及基本的使用,以及了解狀態管理(可參考:[Vuex] 認識 Vue 的好朋友)
不是有 Vuex 嗎?怎麼來了個 Pinia
Pinia 是 Vue3 推出後,官方推薦使用的新狀態管理工具, 也是默認配置。可以將 Pinia 視為 Vuex 5,而 Vuex 也進入維護狀態。
Pinia 和 Vuex 適用 Vue 版本
Vuex
- v4 適用於 Vue3
- V3 適用於 Vue2
Pinia
- Vue3 、Vue2 都支援
Pinia 和 Vuex 之間的差異
取自於 Pinia 官網對比 Vuex 的簡要敘述如下
- Pinia 移除 Mutations
- Pinia 支持 Server Side Rendering
- Pinia 無需設置 namespaced,所有的 store module 都已自動 namespaced
- Pinia 可以直接從 store 取得任何的 State
- Pinia 對 TS 有著更好的支援,不再需要多餘的 types 來包裝
- Pinia 使用 action 可以直接引入函數,不再需要
- Pinia 取得 state 不再需要傳遞參數,可直接使用「this」取得
- Pinia 可以在 actions 中使用 async/await 取得非同步資料後更改 State
- Pinia 不在是單一 Store,使得我們與 Store 互動 API 比 Vuex 簡單許多
最大的差異在於移除了 Mutations,移除之後再寫法上變得相當的簡潔,但就看不出是使用 Flux 的架構。
Pinia 基本架構(Store、State、Actions)
- Store & State
與 Vuex 完全相同,不再贅述。
- Actions
Pinia 的 Actions 與 Vuex Mutations 操作相同,並支援 async function,這是改變 State 的唯一方法。
一個簡單的 Pania Store 可能如下:
import { defineStore } from "pinia";
export const useCounterStore = defineStore({
id: "counter",
state: () => ({
counter: 0,
}),
getters: {
doubleCount: (state) => state.counter * 2,
},
actions: {
increment() {
this.counter++;
},
reduce() {
this.counter--;
},
},
});
了解 Pinia 的基本用法後,我們接著會來比較寫法上的差異,會比較的寫法有以下三種
- Vue3(Composition API)+Pinia
- Vue2(Option API)+ Pinia
- Vue3(Option API)+ Vuex4
前置安裝說明
vue 起專案可以使用 vue cli 或 npm init 兩種方式:
- vue cli(v5.0.8):沒有安裝 Pinia 的選項,如果要使用 Pinia 需 npm 另外安裝。
- npm(v6.14.5):
npm init vue
會詢問是否要安裝 pinia,沒有安裝 Vuex 的選項,如果要使用 Vuex 需 npm 另外安裝。。
比較1. 註冊套件、創建 Store 並在 component 中引入 State
在註冊的部分 Pinia 與 Vuex 的差異在於,Pinia 默認就是動態創建模組,而 Vuex 需要先把 Store module 建立出來後在進行註冊。
Vue3(Composition API)+Pinia
- createPinia:創建 Pinia 實例
- defineStore:定義 Store
- storeToRefs:由於 Store 是 reactive 物件,需要解構才能取出 State
// ----- main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
// ----- stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore({
id: 'counter',
state: () => ({
counter: 0
}),
})
// ----- app.js
<script setup>
import { storeToRefs } from 'pinia';
import { useCounterStore } from "./stores/counter";
const counterStore = useCounterStore()
const { counter } = storeToRefs(counterStore);
console.log(counter)
</script>
<template>
{{counter}}
</template>
Vue2(Option API)+ Pinia
PiniaVuePlugin
:需要在 main.js 注入才能使用 Pinia- mapState :在 component 中使用 State
// ----- main.js
import Vue from 'vue'
import App from './App.vue'
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.config.productionTip = false
Vue.use(PiniaVuePlugin);
const pinia = createPinia();
new Vue({
pinia,
render: h => h(App),
}).$mount('#app')
// ----- stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore({
id: 'counter',
state: () => ({
counter: 0
}),
})
// ----- app.js
<template>
<div id="app">
{{counter}}
</div>
</template>
<script>
import { mapState } from 'pinia';
import { useCounterStore } from "./stores/counter";
export default {
name: 'App',
computed: {
...mapState(useCounterStore, ['counter']),
}
}
</script>
Vue3(Option API)+ Vuex4
- createStore:建立初始 State 並註冊
- mapState:在 component 中使用 State
// ----- stores/index.js
import { createStore } from 'vuex'
const state = {
counter: 0,
}
const store = createStore({
state,
})
export default store
// ----- main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App)
.use(store)
.mount('#app')
// ----- app.js
<template>
Counter: {{ counter }}
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'App',
computed: {
...mapState(['counter'])
}
}
</script>
比較2. 建立 Actions,並在 Component 中事件觸發 Action
在事件觸發 Action 的部分 Pinia 與 Vuex 的差異在於,Pinia 不用定義 mutation,可直接在 Action 的部分改變 State 的狀態。
Vue3(Composition API)+Pinia
- 可直接在解構完的 Store 中使用點運算子取得 Ation
// ------ /stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore({
id: 'counter',
state: () => ({
counter: 0
}),
actions: {
increment() {
this.counter++
},
decrease() {
this.counter--
}
}
})
// ------ App.js
<script setup>
import { storeToRefs } from 'pinia';
import { useCounterStore } from "./stores/counter";
const counterStore = useCounterStore()
const { counter } = storeToRefs(counterStore);
const doDecrease = () => {
counterStore.decrease()
}
const doIncrement = () => {
counterStore.increment()
}
</script>
<template>
<button @click="doDecrease">-</button>
Counter: {{ counter }}
<button @click="doIncrement">+</button>
</template>
Vue2(Option API)+ Pinia
mapActions
:取得 Action
// ------ stores/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore({
id: 'counter',
state: () => ({
counter: 0
}),
actions: {
increment() {
this.counter++
},
decrease() {
this.counter--
}
}
})
// ------ App.js
<template>
<div id="app">
<button @click="decrease">-</button>
Counter: {{ counter }}
<button @click="increment">+</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia';
import { useCounterStore } from "./stores/counter";
export default {
name: 'App',
methods: {
...mapActions(useCounterStore, ['increment', 'decrease']),
},
computed: {
...mapState(useCounterStore, ['counter']),
}
}
</script>
Vue3(Option API)+ Vuex4
// ------ stores/index.js
import { createStore } from 'vuex'
import modules from './modules'
const state = {
counter: 0,
}
const mutations = {
increment: (state) => state.counter++,
decrease: (state) => state.counter--,
}
const actions = {
increment: (context) => {
context.commit('increment')
},
decrease: (context) => {
context.commit('decrease')
},
}
const store = createStore({
state,
mutations,
actions,
})
export default store
// ------ App.js
<template>
<button @click="decrease">-</button>
Counter: {{ counter }}
<button @click="increment">+</button>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
name: 'App',
methods: {
...mapActions(['increment', 'decrease']),
},
computed: {
...mapState(['counter'])
}
}
</script>
結語
以上為 Pinia 的基本用法,並同時附上 Vuex 的用法進行比較,我們可以發現,Pinia 的寫法簡潔非常多!並更直觀,把 Vuex 長久以來的詬病(Muation、引入各種 magic string)進行了優化,希望大家也可以試試看這新的 Store 管理套件。
參考資料
以上有任何錯誤的地方歡迎指正,感謝。