Vue 3 狀態管理其它方案!跟 Vuex 說再見 ?

上一篇「升級 Vue 3 前,你必須要知道的改動」提到 Vue 3 移除了 $on$off$once,所以Vue 2 EventBus 做法已經不能使用,雖然官方有提到替代方案 mitt ,但除了使用套件以外有沒有其他做法呢?

答案是有的!

這篇就要來教大家如何在 Vue 3 實作狀態管理,除了可以達到 EventBus 效果,「或許」也可以替代 Vuex。

簡介狀態管理並實作

以 Vuex 為例,狀態管理除了需要可以「全域共用」還需要符合下面幾個條件

1. 即時響應

當狀態改變時,所有組件都能即時更新對應的數據。而 Vue 3 提供了reactive 方法,可以即時響應值的改動( 底層是用 Proxy 實作的 )。

實作:

  1. 建立一個 store.js,並新增一個 function: createStore
  2. 新增 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 物件變成唯讀狀態。

實作:

  1. 修改 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 還有以下優點:

  1. 可以在 dev tools 檢視紀錄,方便 debug。
  2. 可以模組化程式 (module handling),想像一下如果有多個 Provide 你會怎麼寫呢?
  3. 省去重新造輪子的時間。

Provide & Inject 做法和 Vuex 都是一種「解決方案」,就看團隊/個人怎麼選擇了!但要在 Vue3 用 EventBus 的話,Provide & Inject 仍然是個不錯的選擇 。

以上如有問題歡迎在底下留言一起討論分享 😄

覺得文章有幫助到你的話,請不要客氣地幫我們 clap 👏。若想要持續關注更多文章可以加入我們的臉書或訂閱 Publication, 兩個都能即時收到新文章通知喔!

--

--

PeerOne Technology 皮偶玩互動科技
PeerOne Technology 皮偶玩互動科技

Published in PeerOne Technology 皮偶玩互動科技

Build up customized cloud system service. We not only execute projects that meet up the needs for the variety of industry corporates but also dedicate to developing our own SaaS product that can solve the daily human problems.