# 管理 Vue 資源狀態
如果說真的與現有的 Vue 架構相比有大幅改善的地方,莫過於是狀態管理了,本章節將展示為什麼不要將後端資源給 Vuex 進行託付的原因,以及如何透過 Alas 改善。

# Vuex
如果你覺得 Model 要定義的東西太多了,那就來看看 Vuex 要承擔 Alas 是什麼樣子,以下例子是需求從單一 Component 轉移到共享狀態時,在沒有 Model 的時候是如何透過 Vuex 完成的:
// store.js
import axios from 'axios'
import { createStore } from 'vuex'
export default createStore({
state: {
user: null,
error: null,
called: false,
loading: false
},
actions: {
async fetch({ context }, username) {
commit('startFetch')
try {
let { data } = await axios.get(`/users/${username}`)
commit('finishFetch', data)
} catch(error) {
commit('errorFetch', error)
}
}
},
mutations: {
startFetch(state) {
state.called = true
state.loading = true
},
finishFetch(state, user) {
state.user = user
state.loading = false
},
errorFetch(state, error) {
state.error = error
state.loading = false
},
clear(state) {
state.user = null
state.error = null
state.called = false
state.loading = false
}
},
modules: {
user: state => state.user,
error: state => state.error,
called: state => state.called,
loading: state => state.loading,
isAdult: state => state.user ? state.user.age >= 18 : null
}
})
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>
<div>Name: {{ user.name }}</div>
<div>Age: {{ user.age }}</div>
<div>{{ isAdult ? '已成年' : '未成年' }}</div>
<button type="button" @click="refresh">Refresh</button>
</div>
</div>
</template>
<script>
import { useStore } from 'vuex'
import { defineComponent, onMounted, reactive } from 'vue'
export default defineComponent({
props: {
username: String
},
setup({ username }) {
const store = useStore()
onMounted(() => {
if (store.getters['called']) {
store.dispatch('fetch', username)
}
})
const refresh = () => {
store.commit('clear')
store.dispatch('fetch', username)
}
return {
refresh,
user: computed(() => store.getters['user']),
error: computed(() => store.getters['error']),
isAdult: computed(() => store.getters['isAdult']),
loading: computed(() => store.getters['loading'])
}
}
})
</script>
Vuex 在處理 動作同步行為(interaction) 是良好的 (這也是 ALAS 不擅長的地方),例如進行拖曳、跳出訊息之類等 Layout Component 就很適合與 Vuex 進行封裝處理,但當目標放在向後端請求資源的時候缺點就會暴露出來。
- TypeScript 支援度很差
- 一個行為要變更的程式碼實在太多
- 當需求變更的時候,Vuex 容易造成其他程式碼一同發生錯誤
- 當規模大時,由於 Vuex 的職責過多,基於屬性 modules 進行擴展的管理並不容易,容易造成開發者的混亂
- 當對 UX 有嚴格的要求的時候,Vuex 會有大量需要特別定義的 state,這將導致過度偶合的問題發生
# Alas Status
ALAS 自己具有建立 Model Group 的功能,稱之為 Status
,以下例子將解釋如何取代 Vuex 管理後端資源的角色:
// root-status.js
import { alas } from '@/alas'
export const rootStatus = alas.registerStatus('RootStatus', {
states: {
user: () => alas.make('*', 'user')
}
})
<template>
<div>
<div v-if="user.$o.fetch.loading">Loading...</div>
<div v-else-if="user.$o.fetch.error">Error: {{ user.$o.fetch.error }}</div>
<div v-else>
<div>Name: {{ user.$v.name }}</div>
<div>Age: {{ user.$v.age }}</div>
<div>{{ state.user.$v.isAdult ? '已成年' : '未成年' }}</div>
<button type="button" @click="refresh">Refresh</button>
</div>
</div>
</template>
<script>
import { rootStatus } from './root-status.js'
import { defineComponent, onMounted } from 'vue'
export default defineComponent({
props: {
username: String
},
setup({ username }) {
const user = rootStatus.fetch('user')
onMounted(() => {
if (user.$o.fetch.called === false) {
user.$o.fetch.start(username)
}
})
const refresh = () => {
rootStatus.reset('user')
user.$o.fetch.start(username)
}
return {
user,
refresh
}
}
})
</script>
在定義好 Model 後基於狀態的延伸變得非常簡單:
- 狀態可以重置
- TypeScript 的支援佳,變動相對容易
- 可以基於 Import 行為去引入 Status,使管理更加容易
- 複合式的狀態由 Model 本身去接手,改善了 Vuex 本身要進行資料改動的困難
# 重製狀態
Status 可以隨時重置成最初宣告時的樣子,你可以在各種層級的 path 中定義自己個別的狀態,減輕 root status 的負擔,使程式碼更加清潔可控:
# 假設我們存在這樣的 Vue Route
const routes = [
{
path: 'users/:user',
component: () => import('./users/main.vue'),
children: [
{
path: '/',
component: () => import('./users/overview.vue')
}
]
}
]
# 在這個資料夾結構中加入專屬的 status 檔案
|── router.js
└── users
├── status.js
├── overview.vue
└── main.vue
當進入這個頁面的時候加載 user 資料,但在離開的時候重置 user 的資料:
<!-- main.vue -->
<template>
<div>
<div v-if="user.$ready === false">Loading ...</div>
<router-view v-else></router-view>
</div>
</template>
<script>
import router from '../router.js'
import { status } from './status.js'
import { defineComponent, onMounted, onUnmounted } from 'vue'
export default defineComponent({
setup() {
const user = status.fetch('user')
onMounted(() => {
const username = router.currentRoute.value.params.user
user.$o.fetch.start({
username
})
})
onUnmounted(() => {
status.reset('user')
})
return {
user
}
}
})
</script>
當我們進入 overview 的時候就能確保 user 的資料已經存在:
<!-- overview.vue -->
<template>
<div>{{ user.name }}</div>
</template>
<script>
import { status } from './status.js'
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
return {
user: status.fetch('user')
}
}
})
</script>