Vue 3 狀態管理其它方案!跟 Vuex 說再見 ?
上一篇「升級 Vue 3 前,你必須要知道的改動」提到 Vue 3 移除了 $on
、$off
、 $once
,所以Vue 2 EventBus 做法已經不能使用,雖然官方有提到替代方案 mitt ,但除了使用套件以外有沒有其他做法呢?
答案是有的!
這篇就要來教大家如何在 Vue 3 實作狀態管理,除了可以達到 EventBus 效果,「或許」也可以替代 Vuex。
簡介狀態管理並實作
以 Vuex 為例,狀態管理除了需要可以「全域共用」還需要符合下面幾個條件
1. 即時響應
當狀態改變時,所有組件都能即時更新對應的數據。而 Vue 3 提供了reactive 方法,可以即時響應值的改動( 底層是用 Proxy 實作的 )。
實作:
- 建立一個 store.js,並新增一個 function: createStore
- 新增 reactive 物件,相當於 Vuex 的 state
我們利用 reactive 可以即時響應的特性完成了我們的第一步!
import { reactive } from 'vue'export const createStore = () => {
const state = reactive({
age: 0,
name: '',
}) return {
state
}
}
2. 不可直接修改
在 Vuex 若要修改狀態會需要用 commit mutations 確保狀態不會直接被修改。而在 Vue3 提供了 readonly 方法可以讓 reactive 物件變成唯讀狀態。
實作:
- 修改 store.js,使用 readonly 確保 state 只能被讀取。
import { reactive, readonly } from 'vue'export const createStore = () => {
const state = reactive({
age: 0,
name: '',
}) return {
state: readonly(state)
}
}
直接修改 state 會發出警告訊息
const { state } = createStore()state.name = 'peerone' // warning!
2. 若要修改 state 我們可以這樣寫,fetchUserData 相當於 Vuex 的 mutations 或 actions
export const createStore = () => {
const state = reactive({
age: 0,
name: '',
}) const fetchUserData = () => {
return new Promise((resolve) => {
setTimeout(_=> {
state.age = 18
state.name = 'peerone'
resolve()
}, 1000)
})
} return {
fetchUserData,
state: readonly(state)
}
}
3. 可以搭配 computed,做到 Vuex getters 的效果
補充:Vuex getters 背後也是用 computed 做到緩存效果。
const getName = computed(_=> state.name)
3. 全域可使用的 「命名」
Provide 和 Inject 其實在 Vue 2.x 就已經存在,但用的機會比較少。
我們用圖讓大家快速複習一下這兩個 function。
- 在 A 使用 provide,B、C、D、E 可以 inject
- 在 B 使用 provide,D、E 可以 inject
- 在 C 使用 provide,沒有組件可以 inject
- 在 D 使用 provide,沒有組件可以 inject
- 在 E 使用 provide,沒有組件可以 inject
總結上述
只要確保「父」組件 provide「子」組件就可以使用 inject
B組件提供 'foo'const Provider = {
provide: {
foo: 'bar'
},
}...D和E组件注入 'foo'const Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
}
Provide 可以使用 Symbol 作為 key 使用,確保 key 是唯一的,但要怎麼當作「全域的命名」來使用呢?
實作:
首先在 store.js 中 export 一個 Symbol 物件
export const state = Symbol('state')
因上述原理我們在 main.js 使用 state 作為 provide 的 key,這樣子 App.vue 底下的組件都可以 inject 到 state
import { createApp } from 'vue'import App from './App.vue'import { state, createStore } from './store' createApp(App)
.provide(state, createStore())
.mount('#app')
而 Inject 的部分我們需要在 store.js export 另一個 function
import { reactive, readonly, inject } from 'vue'export const state = Symbol('state')export const useState = () => inject(state)
接著在想要取用狀態的組件使用 useState 就可以了!
<template>
<div>{{ state }}</div>
</template><script>
import { useState } from '../store'export default {
setup() {
const { state } = useState() return {
state
}
}
}
</script>
store.js 完整程式碼
import { reactive, readonly, inject } from 'vue'export const state = Symbol('state')export const useState = () => inject(state)export const createStore = () => {
const state = reactive({
age: 0,
name: '',
}) const fetchUserData = () => {
return new Promise((resolve) => {
setTimeout(()=>{
state.token = 18
state.name = 'peerone'
resolve()
}, 2000)
})
} return {
fetchUserData,
state: readonly(state)
}
}
驗證結果
基本的 store 已經完成了 🎉,接下來我們來驗證一下能不能達到狀態管理的功能吧!(不要忘記在 main.js 使用 provide)
在 App.vue 增加兩個 component
<template>
<div class="flex">
<Action />
<Result />
</div>
</template><script>
import Action from './components/Action.vue'
import Result from './components/Result.vue'export default {
name: 'App',
components: {
Action,
Result
}
}
</script>
Action.vue 和 Result.vue 都用了 useState
Action.vue 會更新 state<template>
<div class="container">
<h5>我是 Action.vue</h5>
<button @click="fetchUserData">Fetch User Data</button>
</div>
</template><script>
import { useState } from '../store'export default {
setup() {
return {
...useState()
}
}
}
</script>Result.vue 會顯示 state 更新後的結果<template>
<div class="container">
<h5>我是 Result.vue</h5>
<div>{{ state }}</div>
</div>
</template><script>
import { useState } from '../store'export default {
setup() {
return {
...useState()
}
}
}
</script>
來看看執行的結果 🎉
結語
以上做法真的可以取代 Vuex 嗎?筆者認為是否定的!
( 所以開頭用了「或許」😂 )
Vuex 還有以下優點:
- 可以在 dev tools 檢視紀錄,方便 debug。
- 可以模組化程式 (module handling),想像一下如果有多個 Provide 你會怎麼寫呢?
- 省去重新造輪子的時間。
Provide & Inject 做法和 Vuex 都是一種「解決方案」,就看團隊/個人怎麼選擇了!但要在 Vue3 用 EventBus 的話,Provide & Inject 仍然是個不錯的選擇 。
以上如有問題歡迎在底下留言一起討論分享 😄
覺得文章有幫助到你的話,請不要客氣地幫我們 clap 👏。若想要持續關注更多文章可以加入我們的臉書或訂閱 Publication, 兩個都能即時收到新文章通知喔!