【Nuxt3】どこからでも呼び出せるトーストを作成してみた
右下にトーストを表示させる話で、色々と使い道がある関係でpages
やcomponents
のvueファイルに記述するとクローンコードだらけになってしまいそうなので、app.vue
に設置した話。
こんなトースト
環境
OS: MacOS
Node: v22.14.0
Nuxt: 3.15.4
Vue: latest
@nuxtjs/tailwindcss: 6.13.1
自作の社内コンポーネントライブラリ
Editor: Cursor
トーストのpluginを作る
composablesかpluginsかという分かりにくさがNuxtにはありますが、
トーストの表示する文章などを指定するステートレスな関数にしたいためpluginsを採用しました。🍍piniaは↑の解説を見る限りにDBっぽい使われ方をするものなので、データを裏で保持しておく必要のない今回は使っていません。
toast
とshowtoast
の二つをprovideしています。
import { reactive } from 'vue'
export type ToastState = {
label: string
message: string
variant: 'success' | 'error' | 'warning' | 'info'
show: boolean
cancelable: boolean
}
//showの値で表示非表示を決めています
export const toastState = reactive<ToastState>({
label: '',
message: '',
variant: 'info',
show: false,
cancelable: false
})
//自作のコンポーネントに渡すprops
interface ToastOptions {
label: string
message: string
variant?: ToastState['variant']
cancelable?: boolean
}
export const showToast = ({label, message, variant = 'info', cancelable = false}: ToastOptions) => {
toastState.label = label
toastState.message = message
toastState.variant = variant as ToastState['variant']
toastState.show = true
toastState.cancelable = cancelable
if(!cancelable) {
setTimeout(() => {
toastState.show = false
}, 3000)
}
}
// cancelable === true時に使うcloseToastはまだ作ってない
export default defineNuxtPlugin(() => {
return {
provide: {
toast: toastState,
showToast
}
}
})
トーストを表示させる
先ほど作ったpluginを使って、実際にトーストを表示させます。
まずトーストを表示させる場所については、全ページで使うためapp.vue
に設置します。
- fixedを使うことで画面の所定の位置(右下)に表示されるように
- トーストはクライアントサイドでレンダリングされる機能なのでClientOnlyを使うことでHydrationエラーを防ぐ
<script setup lang="ts">
import { Toast } from 'my-template';
const { $toast } = useNuxtApp()
</script>
<template>
<NuxtLayout>
<NuxtPage />
<ClientOnly>
<div v-if="$toast.show" class="w-[400px] fixed bottom-4 right-4">
<Toast :label="$toast.label" :message="$toast.message" :variant="$toast.variant" :cancelable="$toast.cancelable" />
</div>
</ClientOnly>
</NuxtLayout>
</template>
これで$toast.show === true
となるとトーストが表示されるようになったので、showtoast
関数を使ってトーストを表示させます。
記事の冒頭のスクショの、ログインが必要なページに非ログイン状態でアクセスしようとした際の処理はこんな感じになります。
const { $showToast } = useNuxtApp();
const { t } = useI18n();
const route = useRoute();
if (route.query.reason) {
switch (route.query.reason) {
case "unauthorized":
$showToast({
label: t("toast.auth.unauthorized.label"), //vue-i18n
message: t("toast.auth.unauthorized.message"),
variant: "error",
});
break;
case "session_expired":
$showToast({
label: t("toast.auth.session_expired.label"),
message: t("toast.auth.session_expired.message"),
variant: "error",
});
break;
default:
break;
}
}
const { loggedIn } = useUserSession() //Nuxt-Auth-Utilsの関数
if (!loggedIn.value && to.path !== '/login') {
return navigateTo({
path: '/login',
query: { reason: 'unauthorized' },
replace: true
})
}
まとめ:props/emit地獄からの脱出
過去にvueでコーディングしていた時はcomposablesやpluginsの概念を知らなかったので、親コンポーネント/ページで使っているコンポーネントにデータを渡すために、$emit地獄になっていたか、vuexあるいはpiniaといったstoreを使って、常に保持しているデータに気を使って開発しないといけない状況になっていました。
pluginsとTS[1]の組み合わせで、これだけ楽に各ページ、コンポーネントで何も気にせずに呼び出せるようになって感動しています。
-
型指定=必須パラメータを指定することでメッセージがnullになったり前のトーストのものになってしまう問題を防ぐ ↩︎
Discussion