# 開獎網 - useQueryFetch ![彩球](http://pic.pimg.tw/beckham1985/fa315ec6dee5dfacaf767fba6bd6c617.png =400x) 因為客戶想要一個可以賺廣告收入的形象網站<br/> 新的越南彩票開獎網 **Aphid** 就這麼出生了<br/> (中國彩叫 **Ant** 但我不想浪費時間想專案名就隨興取一個跟螞蟻互利共生的昆蟲了) ### 業務需求 初次拿到的頁面請求要印在 **html** 上,<br/> 其他的一律用 JS 去打請求,<br/> 對~很典型的 SSR 情境。 因為有開獎相關的資訊會有分頁,<br/> 且選到其他控因都要**重置頁碼**,<br/> 所以我們一樣封裝一個 hook 處理,<br/> 可以參考我以前的文章:<br/> [前台重構 - 使用 Composition API 封裝頁碼邏輯](https://hackmd.io/@lilybon/use-pagination) 不過跟之前不同,<br/> 這次專案的規模很小,<br/> 且後端並沒有用到 **LimitOffsetPagination**,<br/> 所以不需要用 computed 額外處理邏輯 page 的邏輯。(讚喔) 身為這次專案的 TeamLead,<br/> 為了組員幸福及害怕重功惡夢,<br/> 只好再造一次給 Nuxt 用的業務輪子。 ![Ah shit, here we go again.](https://memes.tw/user-template/3c868dfa0d6fbe73178ec786504a5a56.png =400x) ### 思路 1. 事件順序是 conditions 或 page 產生變化 → 更新 router query → 打請求 2. 有別於之前 usePagination 的設計 後來覺得既然 conditions 都跟 query 高度相關,乾脆直接強制使用者定義 **setupConditions** 讓 query 知道如何去初始化或更新 conditions;此外,考慮到依賴 route query 這個核心變數,不用 useAsync 而用 **useFetch**。 ### 困境與解決方法 1. nuxtjs/composition-api 的 useFetch **不提供傳參數**,必須要自己在 hook 裡面拼接成它要求的規格。 2. 要考慮從 `nuxt-link` 觸發改動 route query 的情境,要回流更新 conditions 跟 page 讓 UI 保持一致,但要避免不必要的 router push 和避免 watch 無窮迴圈。 ### 實作 ```javascript import { computed, ref, reactive, useRoute, useRouter, useFetch, watch, } from "@nuxtjs/composition-api"; import { isSameObj, purifyObj } from "~/utils"; export default function ({ setupConditions, defaultParams, promiseFn, afterFetch, }) { // 防呆 setupConditions = setupConditions || (() => { throw new TypeError( "You must add setupConditions to teach useQueryFetch how router query turns into conditions!" ); }); afterFetch = afterFetch || (() => { throw new TypeError( "You must add afterFetch to receive promise response data!" ); }); const router = useRouter(); const route = useRoute(); const query = computed({ get: () => route.value.query, set: () => {}, }); const { page: parsePage, ...parseConditions } = query.value; const page = ref(+parsePage || 1); const conditions = reactive(setupConditions({}, parseConditions)); // 在 hook 中以匿名函數組成符合 useFetch 用法的函數 const { fetch, fetchState } = useFetch(async () => { const data = await promiseFn({ ...query.value, ...defaultParams, }); afterFetch(data); }); const updateRouteQuery = (newQuery) => { // 避免回流觸發無意義的 router push 和 url 髒掉,ex: xxx.com?a=&b=&c=&d=wtf const purifyNewQuery = purifyObj(newQuery); const purifyOldQuery = purifyObj(query.value); if (isSameObj(purifyNewQuery, purifyOldQuery)) return; router.push({ name: router.currentRoute.name, query: purifyNewQuery, }); }; watch( () => ({ ...conditions }), (newConditions) => { updateRouteQuery({ ...newConditions, page: 1, }); }, { deep: true } ); watch(page, (newPage) => { updateRouteQuery({ ...conditions, page: newPage, }); }); watch(query, (newQuery) => { // 考慮直接點選 nuxt-link 時要回流更新 conditions 跟 page const { page: parsePage, ...parseConditions } = newQuery; setupConditions(conditions, parseConditions); page.value = +parsePage || 1; fetch(); }); return { page, conditions, fetch, fetchState, }; } ``` ### 使用方式 以下為任意一個 page 使用 useQueryFetch 的範例: ```javascript import { defineComponent, ref, useContext } from "@nuxtjs/composition-api"; import useQueryFetch from "~/hooks/useQueryFetch"; export default defineComponent({ setup() { const resultData = ref([]); const totalPages = ref(0); const { $axios } = useContext(); const { page, conditions, fetchState } = useQueryFetch({ setupConditions: (conditions, routeQuery) => { conditions.a = routeQuery.a; return conditions; }, defaultParams: { b: 30, c: false, }, promiseFn: (params) => $axios.get("/foo/", { params }), afterFetch: (data) => { resultData.value = data.result_data; totalPages.value = data.total_pages; }, }); return { resultData, totalPages, page, conditions, fetchState, }; }, }); ``` ###### tags: `Work` `JavaScript` `Vue`