Vue3のすゝめ 〜外部テンプレートエンジン編〜

## はじめに

こんにちは！
先日は<a href="https://qiita.com/KentaYokoyama/items/9a4f8b0a3230708a1987" target="_blank" rel="noopener noreferrer">Vue3をアゲアゲ↑↑する記事</a>を投稿し、
ご好評頂けたようでありがとうございます:blush:

X (旧Twitter)を見ている限りだと肯定的な意見が多く、
これを機にVueのご利用を検討頂ける人が増えてくれると嬉しいです。

さてさて記事の中では、
「**out of boxで動作する=外部テンプレートエンジンとも相性が良い**」という話をしたのですが、今回は具体例を交えながらこの点をもっと掘り下げていこうと思います。

:::note info
この記事では、
サーバテンプレートエンジンとしてblade(Laravel9)を例にとって解説していきますが、どのテンプレートでも同様なのでご安心ください。
:::

### ・・実例の前に

そもそも論として、
私はサーバ由来のテンプレートエンジンを使うことが好きではありません。

「フロントはフロントのテンプレートエンジンでやるから、サーバ実装は全部APIでやってくれ」というのが本音ですし、
SPAに慣れているフロントエンジニアの皆さんもほとんど同じような意見かなと思います:confused:

でもそれでも、お仕事なのでこちらの都合が通らない時もあります。
そんな時の処方箋としてもVue3は最適だよっていう話を今回はしたいと思います。

## 下準備

Laravel9はデフォルトがwebpackからViteに変わっています。
まずは公式を見て、エントリーポイントとなるapp.jsの作成と読み込みまで済ませてください。(雑)

https://readouble.com/laravel/9.x/ja/vite.html

次にVueインスタンスの作成ですが、
例えば`id="modal"`の要素に対してVueで作成したモーダルコンポーネントを当てるには以下のようになります。

```html
<div id="modal"></div>
```

```javascript
import CommonModal from './components/common/Modal.vue';

createApp(CommonModal).mount('#modal')
```

**が、** モーダルが増える度に下の行を複製していくのはナンセンスでしょう。

以下のように書くと`#app`配下の要素に対して、
追加のJSの記述なしで**いつでも何回でも**コンポーネントが利用できるようになります。
(#appはblade内のbody直下などにするのが良いかと思います)

```javascript
import CommonModal from './components/common/Modal.vue';

const app = createApp({
  components: {
    CommonModal,
  },
}).mount('#app');

window.app = app;
```

```html
<div id="app">

  <common-modal></common-modal>
  <common-modal></common-modal>

</div>
```

`window.app = app;`と最終行に書いてVueインスタンスをグローバルに公開しているのですが、これはちょっとしたテクニックで後々役に立ってきます。

## モーダル作成

そういえばモーダルファイル作成がまだでしたね、とりあえず最低限の記述として以下のようにしておきましょう。

```vue
<script setup>
import { ref } from 'vue';

// フラグ
const isOpen = ref(false);

// オン/オフ切り替え
const toggleModal = (_animation) => {
  isOpen.value = !isOpen.value;
};

// コンポーネント外に公開
defineExpose({
  toggleModal,
  isOpen,
});
</script>

<template>
  <div v-if="isOpen" class="modal">
    <h3 class="title" v-if="$slots.header">
      <slot name="header" />
    </h3>
    <div class="body">
      <slot />
    </div>
    <div class="footer">
      <button @click="toggleModal">閉じる</button>
    </div>
  </div>
</template>

<style lang="scss" scoped></style>

```

フラグは親からpropsで受け取るのではなく、
コンポーネント自身で保持して`defineExpose()`で**外部に公開するようにしています**。(これも理由があるので後述)

ではblade側にこのモーダルと、オープンするためのボタンを置きましょう。

```html
<common-modal ref="modal">
  <template #title>タイトル</template>
  本文
</common-modal>
<button onclick="app.$refs.modal.toggleModal()">ボタン</button>
<!-- または -->
<button onclick="app.$refs.modal.isOpen = true">ボタン</button>

```

これを見て不思議に思った方がいるかも知れませんが、
ここではVueの`@click`ではなく`onlick`で発火しています。

ご存知ない方もいらっしゃるかと思いますが、
**実はただのJavaScriptのコードでVueコンポーネントを外部から扱えます！**


`app`というのは先ほどエントリーポイントでグローバル(window)に公開したVueインスタンスのことを指しており、
$refsには#app配下で`ref=*`指定しているものが全て入っています。

toggleModalは、モーダルコンポーネント内で`defineExpose()`した関数(=中身はモーダルの開閉をしている)になります。

なぜこんなことをしているかと言うと、
外部テンプレートとVueを使った開発をしていると、
「**Vueコンポーネントを扱うために毎回script用意しないといけないの面倒くさいな**」とか「**こっちはただのJSのコードだけど、こっちはVueのコードで操作しないといけないの分かりづらいな**」とか、思った経験はありませんか・・？

Vueは状態を外に出しても機能するので、
通常のJavaScriptに思いっきり寄せた扱い方もでき、
それにより**blade ⇆ Vue間の摩擦を更に減らすことが可能**になります。

またよくある要素の取得もかなり楽にしてくれます。

例えば以下のようなVueコンポーネントとは全く無関係の要素があった時、

```html
<div>ほげ</div>
<div>ふが</div>
```

jsファイル内でこの要素を取得するのはこんな感じでしょうか？

```html
<div id="hoge">ほげ</div>
<div id="fuga">ふが</div>
```

```javascript
document.getElementById('hoge')
document.querySelector('#fuga')
```

いえいえ、**これで出来ます。**
```html
<div ref="hoge">ほげ</div>
<div ref="fuga">ふが</div>
```
```javascript
const { hoge, fuga } = app.$refs
```

またよく使う処理に関しても、
Vueインスタンス内に含めておけば**好きな時にただのJSの変数/関数**として利用できます。

```javascript
import CommonModal from './components/common/Modal.vue';
import { useUtility } from './composables/useUtility';
import { useAuth } from './composables/useAuth';

const { copyToClipboard } = useUtility();
const { user } = useAuth();

const app = createApp({
  components: {
    CommonModal,
  },
  methods: {
    copyToClipboard
  },
  data: () => ({
    user,
  }),
}).mount('#app');

window.app = app;
```

```html
<button onclick="app.copyToClipboard(app.user)">コピー</button>
```

Vueインスタンス内に含めるのは、
**JSファイルを作らずとも処理や値をhtml側で呼び出せるようにしたい**というモチベーションが個人的に大きいからで、JSファイルを別で作ってimportしてきても勿論大丈夫です。
(むしろグローバルに置くのは見られても問題ない情報に留めましょう)

```javascript
import { watchEffect } from 'vue';
import { useAuth } from '@/composables/useAuth';
const { user } = useAuth();

watchEffect(() => {
  console.log(`${user.value}がログインしました`)
})
```

上記はただのプレーンなJSの記述ですが、
**Vueの状態はVueの外でもどこでも動作、検知するのでちゃんと動作します。神。**

# まとめ

いかがでしたでしょうか？
out of boxで動作することのメリットは至るところに散りばめられていると思います。

特に外部テンプレートエンジンを採用する場合はその理由が顕著になるので、
何を使ったら良いか迷っている方は是非フロントフレームワークにVue3を採用することをご検討してみてください:relaxed:

**以上！**