Vue.jsで使用頻度は高くないが覚えておきたい機能

## 概要
私はVue.jsでの開発を始めて3~4ヶ月程のエンジニアです。その開発の中であまり使わなかったのですが、覚えておけばその場面をスマートに解決できたりするであろう機能な記述を個人的観点で書かせていただきます。

## v-on
### 1. 引数も渡したい場合は`$event`
正直これは使用頻度結構あると思うのですが、最初知らない時に迷ってしまったため記載しておきます。

```vue
<template>
  <div>
    <button @click="handleClick('Button clicked', $event)">Click Me</button>
  </div>
</template>

<script setup lang="ts">
const handleClick = (message: string, event: MouseEvent): void {
  console.log(message);  // 引数として渡されたメッセージ
  console.log(event);    // クリックイベントオブジェクト
}
</script>
```

### 2. モバイルでのスクロールをスムーズにする`passive`修飾子
スクロール系のv-onのイベントにpassive修飾子を与えることでイベントのデフォルト動作をキャンセルしないことをブラウザに知らせることができるため、スクロールパフォーマンスが向上するそうです。

現在使用されているほとんどのブラウザではデフォルトでpassiveがtureになっているので、あえて指定する必要はなさそうです。

https://caniuse.com/?search=passive%20event%20listeners

<details><summary>Tips ： passiveがスクロールをスムーズにする理由</summary>

passiveが指定されていない場合、イベントリスナーがpreventDefault()を呼び出すかどうかを確認するために、ブラウザはイベントを処理する前にイベントリスナーを呼び出す必要があります。これがスクロールイベントのパフォーマンスを低下させる原因の一つになっているそうです。

passiveがtrueの場合、ブラウザはイベントリスナーがpreventDefault()を呼び出さないことを前提に最適化を行います。つまり、passiveがtrueに設定されたイベントリスナーでは、preventDefault()を呼び出すことができなくなります。

スクロールを最適化するための記述ではなく、preventDefault()を呼び出さないことを指定した結果スクロールイベントのパフォーマンスが向上するというイメージです。
</details>

```vue
<template>
  <div @touchmove.passive="handleTouchMove">
    スクロールしてください
  </div>
</template>

<script setup lang="ts">
function handleTouchMove(event: TouchEvent) {
  console.log('Touch move event:', event);
}
</script>

<style scoped>
div {
  width: 100%;
  height: 300px;
  border: 1px solid black;
  overflow: scroll;
}
</style>
```

### 3. 特定のボタンを押しながらでないと反応しなくする修飾子(`shift`,`ctrl`,`alt`)
普通のボタン等に設置しても正直あまり意味がないと思いますが、管理画面などで本来クリックできないような場所に仕掛けておいて開発者専用のページに飛んだりに使えそうだなと感じました。

本当に飛ばれたくない場合には使用しない方が良いと思いますが、最悪何かの偶然で飛ばれても問題ないがというくらいの感じの際には使えるかもしれません。
```vue
<template>
  <div>
    <h1 @click.shift="navigateToAdmin">
      なんら怪しいところのないページ
    </h1>
    <nav>
    ...
    </nav>
  </div>
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router'

const router = useRouter()

const navigateToAdmin = (event: MouseEvent): void {
  console.log('Shift key is pressed:', event.shiftKey)
  if (event.shiftKey) {
    router.push('/admin') // 管理画面へ遷移
  }
}
</script>
```

### 3. 特定のボタンのみに制限する`exact`修飾子
先ほどの`@click.shift="navigateToAdmin"`ですが、「shift + ctrl + click」でも反応してしまいます。`@click.shift.exact="navigateToAdmin"`とすることで「shift + ctrl + click」では反応しないようにできます。「shift + click」の場合のみdeleteUserDataが実行されます。

最初はいつ使うのかなと思ったのですが、一度削除しまうと取り返しがつかなくなってしまうような厳密な動作を求める場面で非常に便利なのではないかなと思いました。

confirmやダイアログで大体確認を挟むので実際は使う機会があまりなさそうではありますが…
```vue
<template>
  <div>
    <button @click.shift.exact="deleteUserData(1)">
      Delete (Shift + Click)
    </button>
  </div>
</template>

<script setup lang="ts">
const deleteUserData = (userId: number) => {
  const confirmed = window.confirm(`ID ${userId}のユーザーのデータを削除しますがよろしいですか？`);
  if (confirmed) {
    // 実際の削除処理を行う
  } else {
    alert('削除は行われませんでした');
  }
}
</script>
```

## v-model
### 1. v-modelの.trim修飾子
.trim修飾子を使用すると、ユーザーの入力から前後の空白が自動的に取り除かれます。
入力した内容の前後になるので中間の空白は保持されます。意図的に空白を残した場合でなければ必須でつけても良さそう

```vue
<script setup lang="ts">
import { ref } from 'vue'

const message = ref<string>('')
</script>

<template>
  <div>
    <input v-model.trim="message" placeholder="メッセージを入力してください">
    <p>メッセージ: "{{ message }}"</p>
  </div>
</template>
```

「　メッセージを　入力しました　　　」→「メッセージを　入力しました」となります。

### 2. v-modelの.number 修飾子

これは意外と使用されているかもしれませんが、入力された内容を`number型`に自動的に変換してくれる修飾子です。数字以外が入力されてしまうと`NaN`をとってしまうので、inputタグに`type="number"`などでの制限が必要かもしれません。

```vue
<script setup lang="ts">
import { ref } from 'vue'

const age = ref<number | null>(null)
</script>

<template>
  <div>
    <input v-model.number="age" type="number" placeholder="年齢を入力してください" />
    <p>年齢: {{ age }}</p>
  </div>
</template>
```
age ：「"123"」 → 「123」

### 3. v-modelの.lazy 修飾子
通常のv-modelの入力イベントではなく、changeイベントでバインディングを行います。つまり、ユーザーが入力を終えてフォーカスを外した時点で値が更新されます。正直パフォーマンスに大きな差が現れることは考えにくいため、使用機会は非常に少ないかもしれません。

```vue
<script setup lang="ts">
import { ref } from 'vue'

const message = ref<string>('')
</script>

<template>
  <div>
    <input v-model.lazy="message" placeholder="メッセージを入力してください">
    <p>メッセージ: "{{ message }}"</p>
  </div>
</template>
```

## ディレクティブ
### 1. 初回のレンダリングしか受け付けない v-once
タイトルの通り`v-once`は要素およびその子コンテンツを初回のレンダリング時に一度だけ描画し、その後の再描画では更新されないようにするものです。なので、静的なコンテンツや頻繁に更新される必要のない部分に適用することで、パフォーマンスを向上させることができます。

リアクティブな値が更新されて画面を再レンダリングする際に仮想DOMとの差分を確認して差分だけレンダリングすると思いますが、その監視対象から外すことでパフォーマンスの向上を図れるらしいです。
```vue
<template>
  <div>
    <p>{{ count }}</p>
    <p v-once>{{ message }}</p>
    <button @click="increment">＋１するボタン</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const count = ref(0);
const message = "ボタンを押すと上の数字が変化するよ";

const increment = () => {
  count.value++;
};
</script>
```
上記の例で言うと「ボタンを押すと上の数字が変化するよ」と言う文言は変わらないので監視対象から外した例です。


### 2. 未加工のテンプレートが表示されるのを防ぐ v-cloak
Vueインスタンスが完全に初期化されるまで、テンプレートの一部を非表示にするために使用されます。通常、v-cloakはCSSと組み合わせて使用され、初期の読み込み時に未コンパイルのテンプレートが一瞬表示されるのを防いでくれます。

マウント時にv-cloakが削除されるイメージです
= マスタッシュ値（ {{}} で埋め込む値のこと）の反映を検知して削除される

```vue
<template>
  <div v-cloak>
    <h1>v-cloakの確認</h1>
    <p>{{ message }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const message = ref('これはマウント後に表示されるメッセージです')
</script>

<style scoped>
[v-cloak] {
  display: none;
}
</style>
```


### 3. コンポーネントの動的バインディングが出来る v-bind:is
v-bind:isはコンポーネントの動的バインディングに使用されますが、その省略形として、:isで記述することも可能です。簡単にいうと表示するコンポーネントを切り替える機能です


```vue:押されるボタンによってログインと登録のダイアログを出し分ける
<template>
  <div>
    <button @click="currentModalContent = 'LoginForm'">Login</button>
    <button @click="currentModalContent = 'SignupForm'">Sign Up</button>
    <modal :is="currentModalContent"></modal>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import LoginForm from './LoginForm.vue';
import SignupForm from './SignupForm.vue';

const currentModalContent = ref('LoginForm');
</script>
```

パッと思い浮かんだのはタブを使用するUIの場合にタブの内容ごとにコンポーネントを出し分けたりする場面で使えそうだなと思いました。場面によってコンポーネントを切り替えたいことはよくあるので私が使えていなかっただけかもしれません。


## templateタグ
### 条件分岐等で余計なタグを避けるには、`<template>`を使用

条件分岐はしたいけど余計なタグは増やしたくないという時には`<template>`タグにディレクティブを記入することで回避できます。

```vue
<template>
　　　　<template v-if="isLoggedIn">
    <p>ログイン状態です</p>
　　　　</template>
　　　　<template v-else>
    <p>ログインしてください</p>
　　　　</template>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const isLoggedIn = ref(false);

const login = () => {
  if(!isLoggedIn){
    isLoggedIn.value = !isLoggedIn.value
  }
}
</script>
```

レンダリング時のDOMの状態
```vue
<p>ログイン状態です</p> <!-- 未ログインの時は文言が変わるだけ -->
```
## ページ間遷移に処理を挟めるナビゲーションガード

以下は、ユーザーが認証されていない場合にログインページにリダイレクトするナビゲーションガードの例です。

```typescript:router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import Login from '../views/Login.vue';

const routes = [
  { path: '/', component: Home },
  { path: '/login', component: Login }
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

router.beforeEach((to, from, next) => {
  const isAuthenticated = false;
  // 認証状態を確認
  if (to.path !== '/login' && !isAuthenticated) {
    next('/login');
  } else {
    next();
  }
});

export default router;
```

## まとめ
今回紹介した機能たちは、どれも開発者によって使う場面が多いかもしれません。個人的にあまり使わなかったけども覚えておくといざという時に便利だなと思った機能をまとめさせていただきました。

読んでいただきましてありがとうございました！