i18nでaタグを設定している時にSPAのページ遷移をさせる方法
i18n(多言語化)のライブラリをウィ用している時にaタグを含んだ形で設定するタイミングがあり、用意させれいるv-html
のディレクティブだと普通のページ遷移が行われてしまうのでそれを自前で実装する必要があった。
実装方法
今回はNuxt3での実装方法について記述していきます。
Nuxtのplugins
ディレクトリにカスタムディレクティブの実装をしていきます。
ディレクティブとは
Vue.jsのディレクティブは、HTML要素に特別な機能を追加するための特別なトークンです。これらは主にデータバインディングやDOM操作に使用されます。Vue.jsでは、ディレクティブは
v-
で始まるプレフィックスを持っています。
例:v-bind、v-model、v-if、v-forなどなど
カスタムディレクティブを追加(./plugins/nuxt-html.ts)
export default defineNuxtPlugin((nuxtApp) => {
const idsWithListeners = new Set<string>();
const generateUuid = () => {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0,
v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
const pushRoute = (event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();
const to = (event?.target as Element)?.getAttribute("href");
if (to) {
useNuxtApp().$router.push(to);
}
return false;
}
const assignAnchorsIds = (html: string) => {
const domParser = new DOMParser();
const doc = domParser.parseFromString(html, "text/html");
const anchors = doc.querySelectorAll("a");
anchors.forEach((anchor) => {
if (!anchor.hasAttribute("id")) {
anchor.setAttribute("id", generateUuid());
}
});
return doc.documentElement.outerHTML;
}
const convertAnchorToNuxtLink = (html: HTMLElement) => {
const anchors = html.querySelectorAll("a");
anchors.forEach((anchor) => {
if (anchor.id && !idsWithListeners.has(anchor.id)) {
const link = document.getElementById(anchor.id);
link?.addEventListener('click', pushRoute, false)
idsWithListeners.add(anchor.id);
}
});
}
const removeListeners = () => {
idsWithListeners.forEach((id) => {
const link = document.getElementById(id);
link?.removeEventListener('click', pushRoute, false)
idsWithListeners.delete(id);
});
}
nuxtApp.vueApp.directive("nuxtHtml", {
mounted(el: HTMLElement, binding) {
el.innerHTML = assignAnchorsIds(binding.value);
convertAnchorToNuxtLink(el);
},
updated(el, binding) {
el.innerHTML = assignAnchorsIds(binding.value);
convertAnchorToNuxtLink(el);
},
beforeUnmount() {
removeListeners();
},
});
});
export default defineNuxtPlugin((nuxtApp) => {
defineNuxtPlugin
は、Nuxt 3 でプラグインを定義するためのヘルパー関数になっております。
Nuxt 3 は Vue 3 の上に構築されており、プラグインシステムも新しいコンセプトを導入しています。この関数を使用することで、Nuxt アプリケーションに追加の機能やサードパーティライブラリを統合するプラグインを簡単に作成できるものになります。
const generateUuid = () => {
generateUuid
関数は、一意の ID を生成するために使用しております。これは、
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx の形式で、各 x と y はランダムな16進数の値に置き換えられます。これにより、HTMLドキュメント内の各 <a>
タグに一意の ID を割り当てるようにして後に取得できるようにしております。
const pushRoute = (event: MouseEvent) => {
pushRoute
関数は、ユーザーがリンクをクリックしたときに標準のブラウザナビゲーション(新しいページへのリダイレクトやページリロード)を防ぎ、代わりに Nuxt.js のルーターシステムを通じてプログラム的なページ遷移を行う役割を担っています。
useNuxtApp().$router.push(to);
useNuxtApp()
は、Nuxt3におけるグローバルなアプリケーションの状態や機能にアクセスするための関数です。
Nuxt3におけるルーティング関連はユーティリティ関数として提供されておりuseNuxtApp
経由でuseRouter
やuseRoute
は提供されております。
const assignAnchorsIds = (html: string) => {
assignAnchorsIds
関数は、与えられた HTML 文字列を解析して、すべての <a>
タグを見つけ、それぞれに ID 属性がなければ UUID を割り当てます。これにより、後でイベントリスナーを正確に追加・削除することが可能になります。
const convertAnchorToNuxtLink = (html: HTMLElement) => {
convertAnchorToNuxtLink
関数は、HTML 内のすべての <a>
タグを取得し、それぞれに対して click
イベントリスナーを追加します。このリスナーは pushRoute
関数を呼び出し、Nuxt.js のルーターを使用してページ遷移を行います。これにより、通常のページリロードを防ぎながらアプリケーション内のナビゲーションを実現します。
const removeListeners = () => {
removeListeners
関数は、すべての登録された <a>
タグから click
イベントリスナーを削除します。これはコンポーネントがアンマウントされる前に行われ、メモリリークを防ぐのに役立ちます。
nuxtApp.vueApp.directive("nuxtHtml", {
nuxtHtml
というカスタムディレクティブを定義しており、これを使用すると、指定された要素の innerHTML
にバインディングされた値(HTMLコンテンツ)を設定し、その中の <a>
タグを自動的に処理します。
追加したpluginのファイルをnuxt.config.tsに設定させる
plugins: [
'~/plugins/nuxt-html',
],
設定ができたらVueファイルで使用
<script setup>
const text = '<a href="/news/1">これはテストのお知らせです。</a>'
</script>
<template>
<div v-nuxt-html="text"></div>
</template>
おわりに+会社宣伝
上記コードに関してはあくまで一例ですのでここから各仕様に合わせてカスタマイズしていただけたらなと思います!
Discussion