架構設計優化-路由狀態管理
透過 Vue Router 管理列表頁的篩選、排序與搜尋狀態...等等功能,實現 URL 同步與更好的使用者體驗。監聽路由變化自動觸發 API 請求,讓列表功能更乾淨好維護。
舉例: 在列表中常需要 keyword、sortBy、sortOrder、filter,進行 get api,更新列表
<template>
<div class="product-list">
<div class="filters">
<input
v-model="keyword"
placeholder="搜尋商品..."
@input="updateQuery"
/>
<select v-model="sortBy" @change="updateQuery">
<option value="">排序方式</option>
<option value="name">名稱</option>
<option value="price">價格</option>
</select>
<select v-model="sortOrder" @change="updateQuery">
<option value="asc">ASC</option>
<option value="desc">DESC</option>
</select>
<label>
<input
type="checkbox"
name="男性"
value="men"
v-model="filter"
@change="updateQuery"
/>
</label>
<label>
<input
type="checkbox"
name="女性"
value="women"
v-model="filter"
@change="updateQuery"
/>
</label>
</div>
<div class="products">
<div v-for="product in products" :key="product.id">
{{ product.name }} - {{ product.price }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
const keyword = ref('')
const sortBy = ref('')
const sortOrder = ref('asc')
const filter = ref(['men','women'])
const products = ref([])
const getList = async () => {
const { data } = await product.getList({
keyword: keyword.value,
sortBy: sortBy.value,
sortOrder: sortOrder.value,
filter: filter.value
})
products.value = data
}
// 然後再透過 watch 監聽這些值,如果更動,則會重新 get api
watch([keyword,sortBy,sortOrder,filter],()=>{
getList()
})
onMounted(() => {
getList()
})
</script>
上述方法會有的一些情況是:
- 重新整理頁面後狀態丟失
- 無法分享當前篩選狀態的連結
- 瀏覽器前進後退功能失效
- 狀態管理邏輯散落在各個組件中
透過上述這個方法,仍然可以實現更新列表,但如果讓這些透過使用者調整而變動的值,可以用路由去處理,就會讓使用者體驗比較好,同時也可以專注在 route.query 的操作,再抽取重複的邏輯出來,後續可以更好維護。
/composables/useQueryBinding/index.ts
import type { UseQueryBinding } from '@/composables/useQueryBinding/types';
export const useQueryBinding = <T extends readonly Ref<unknown>[]>(options: UseQueryBinding<T>) => {
const { sources, onQueryChange, transform } = options;
const router = useRouter();
const route = useRoute();
const updateQueryFromSources = () => {
const query = transform(sources);
router.push({
name: route.name,
query,
});
};
watch(sources, updateQueryFromSources);
if (!onQueryChange) return;
watch(
() => route.query,
() => {
onQueryChange();
},
);
};
/composables/useQueryBinding/types.d.ts
export interface UseQueryBinding<T extends readonly Ref<unknown>[]> {
sources: T;
onQueryChange?: () => void;
transform: (sources: T) => Record<string, string | string[] | undefined>;
}