Skip to content
On this page

架構設計優化-路由狀態管理

透過 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>;
}

This site uses open-source libraries.