NuxtJSでSSR + SPAでコンテンツをaxiosで取得するよくあるページを作っていたら、リンクを連打するとこんな感じになった。
言葉で説明するの大変なのでブログ用にTwitterに動画あげた。
NuxtJSなどのSPA + Ajaxでコンテンツを非同期取得してるやつで、ページリンク連打すると表示も非同期になるよくあるやつ。axiosのcancelToken使って解決できた。※画面は開発中(略 pic.twitter.com/5hpBJRnsFp
— uyamazak - 🐦 (@uyamazak) 2021年6月24日
バージョンはここらへん。TypeScript、Composition APIを使ってます。
"dependencies": { "@nuxtjs/axios": "^5.13.4", "@nuxtjs/composition-api": "^0.23.4", "@nuxtjs/proxy": "^2.1.0", "cookie-universal-nuxt": "^2.1.4", "core-js": "^3.12.1", "dayjs": "^1.10.4", "nuxt": "^2.15.6", "vue-slick-carousel": "^1.0.6", "vuejs-paginate": "^2.1.0" },
解決策としては、フォームみたいに、完了するまでクリックできなくするっていう方法もあるけど、ページ送りみたいな普通のリンクではやりたくない。
そこで、連打されたとき前回のをキャンセルする方法ないかと調べたところ、axiosにそういう機能がついてることを知った。
NuxtJSのaxiosモジュールを使っているけど使い方はaxiosと同じ
https://axios.nuxtjs.org/usage/#cancel-token
最初Promiseでできるかと思ったけど難しそうだった。
やってることはこの記事と同じだけど、NuxtJSだったり、Composition APIだったり、TypeScriptだったりするのでメモ。
siosio.hatenablog.com
開発中だけど必要そうなコードを抜粋、編集したやつ(動かない
import { defineComponent, useAsync, useContext, ssrRef, watch, ref } from '@nuxtjs/composition-api' import { JobListApiResponse, Job } from '../types' export default defineComponent({ validate({ query }) { if (!query.page) { return true } else if (typeof query.page === 'string') { return /^\d+$/.test(query.page) } return false }, setup() { const { $axios, query } = useContext() const cancelToken = $axios.CancelToken const cancelSource = ref(cancelToken.source()) const toPageNumber = (number: any): number => { // validateでチェックしてるので最低限 if (typeof number === 'string') { return Number(number) } return 1 } currentPage.value = toPageNumber(query.value.page) const fetchJobs = async () => { const page = toPageNumber(query.value.page) // 前回のをキャンセル cancelSource.value.cancel('連打かな?') // 新しくセット cancelSource.value = cancelToken.source() try { const response = await $axios.$get<JobListApiResponse>( '/api/path', { params: { page }, cancelToken: cancelSource.value.token } ) // response使ってデータをセットする処理 } catch (error) { console.error(error) } } useAsync(fetchJobs) // queryに変更あったら再実行 watch(query, fetchJobs) return { // 省略 } }
cancelSourceはあとでファイルを分けたときようにrefを使ってます。
完了したやつをcancelしても特にエラーにはならないのでそのままキャンセルしてます。
あとリクエスト中のロード画面とかは必要そう。
こうすることで、最後のリクエストだけが残って思ってた挙動になりました