vuetify環境構築とやたら出てくるv-slotについておさらい


[前回記事](https://qiita.com/Kept1994/items/135cbd6795deb13f928b)でvueとexpressでフロントエンドとバックエンドを繋ぐことができるようになった。
次はフロントエンド側を綺麗に見せるためにvuetifyというUIフレームワークをいれてみる。

# vuetifyをインストール

※ 実行すると、App.vueやmain.ts、package.jsonなど複数のファイルがvuetifyのデフォルトに上書きされる。実行前にどっかに退避させておくのが大事。

`package.json`を含むディレクトリ（`$vue create front`でプロジェクトfrontを作成していればfront下にある）で以下のコマンド実行。

```
$ vue add vuetify
📦  Installing vue-cli-plugin-vuetify...

+ vue-cli-plugin-vuetify@2.0.5
added 4 packages from 4 contributors and audited 40952 packages in 8.254s
found 25 vulnerabilities (20 low, 5 moderate)
  run `npm audit fix` to fix them, or `npm audit` for details
✔  Successfully installed plugin: vue-cli-plugin-vuetify

? Choose a preset: Default (recommended)

🚀  Invoking generator for vue-cli-plugin-vuetify...
📦  Installing additional dependencies...

added 2 packages from 1 contributor and audited 40959 packages in 11.674s
found 25 vulnerabilities (20 low, 5 moderate)
  run `npm audit fix` to fix them, or `npm audit` for details
⚓  Running completion hooks...

✔  Successfully invoked generator for plugin: vue-cli-plugin-vuetify
   The following files have been updated / added:

     src/assets/logo.svg
     src/plugins/vuetify.ts
     vue.config.js
     package-lock.json
     package.json
     public/index.html
     src/App.vue
     src/components/HelloWorld.vue
     src/main.ts

   You should review these changes with git diff and commit them.

 vuetify  Discord community: https://community.vuetifyjs.com
 vuetify  Github: https://github.com/vuetifyjs/vuetify
 vuetify  Support Vuetify: https://github.com/sponsors/johnleider

$ npm run serve
```

新しくなったvuetifyのページが現れる。App.vueは消え去っている。。

まずは、エラーを消す作業から。

参考としたのはこの2つ

* https://dev.classmethod.jp/client-side/spa/getting-started-class-style-vuetify/
* https://github.com/vuetifyjs/vuetify/issues/8289

2020年3月現在では`tsconfig.json`の修正のみでエラーはなくなった。

```
  "types": [
      "webpack-env",
      "mocha",
      "chai",
      "vuetify"  //これを追記。
  ]
```

その後失われたApp.vueを元に戻す。

なお、無事インストールできたのにこんな感じのエラーが出てフロントエンドが立ち上がらない場合はサーバーを再起動してもう一回`npm run serve`すると治る。

> throw er; // Unhandled 'error' event

参考) https://qiita.com/M-ISO/items/d693ac892549fc95c14c

# 基本的な使い方

App.vueなどを参考にするとわかるが、vuetifyでは必ず`<v-app></v-app>`で囲われた中で各種タグを使う必要がある。

```vue
<template>
    <v-app>
        ここでv-〇〇達を使ってあげる。
    </v-app>
</template>
```

# v-slot

vuetifyを見ていると`v-slot`を使うものがいくつか見つかる。
正直使わないから飛ばしててまともに知らなかったので勉強。

これまでコンポーネントの親子の関係では親から子に変数やjsonなどの値をプロパティとして渡し、値をどう表示するかは子コンポーネント側で指定していた。ボタンに表示する文言を親から受け取るけれど、どこに配置するかなどは子側で決めるといった具合だろうか。
slotは、子コンポーネントの中身を直接ハードコードせず、親側で動的に決められるようにした仕組み。これにより、vuetifyなどのUIフレームワークに対して柔軟に値をわたし、表現することができるようになっている。

スロットでは、子コンポーネントの中で`<slot></slot>`とした部分に対し、親側から指定されたテンプレートがまるごとはめ込まれる。
slotタグの間にはデフォルトの表示を指定しておくこともできる。

## 名前付きスロット

また、以下のように複数のスロットを名前で区別することもできる。(以下公式リファレンスの例)

```vue:子base-layoutコンポーネント
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
```

```vue:親
<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>   *
  <p>And another one.</p>                    *

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>
```

* 子コンポーネントで名前を指定しないもの(main)はdefaultという名前が暗黙裡につけられる。
* 親側で名前を指定しない箇所(*)はdefaultに入る。`<template v-slot:default>`とすることも可能。

## スコープ付きスロット

子のプロパティに対してslotで流し込むテンプレートの中からアクセスしようとするとundefinedとなってしまい、アクセスできない。
これを回避するためにスロットに対してスコープを当てる。

```vue:子current-userコンポーネント
<span>
  <slot v-bind:user="user">  
    {{ user.lastName }}
  </slot>
</span>
```

```vue:親
<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

//もしくは省略形

<current-user>
  <template v-slot:default="{ user }">
    {{ user.firstName }}
  </template>
</current-user>

//もっと省略形

<current-user>
  <template #default="{ user }">
    {{ user.firstName }}
  </template>
</current-user>
```

上記は親が子のプロパティをslotPropsとして受け取り、そのなかから必要なuserの中のfirstNameにアクセスして表示するという流れ。slotPropsは特になんでもいいしなんなら省略形で書くことで省ける。

# 一例 v-menu

実際に`v-slot`が使われる1つの例として`v-menu`がある。
以下の例ではアイコン`v-icon`を格納したボタン`v-btn`をスロットに渡している。クリック時にコンポーネントが有効になる。

```vue
<v-menu
    left
    bottom
>
    <template v-slot:activator="{ on }">
    <v-btn icon v-on="on">
        <v-icon small>mdi-arrow-down-drop-circle</v-icon>
    </v-btn>
    </template>

    <v-list>
        <v-list-item
            v-for="n in 5"
            :key="n"
            @click="() => {}"
        >
            <v-list-item-title>Option {{ n }}</v-list-item-title>
        </v-list-item>
    </v-list>
</v-menu>
```

`v-menu`はdefaultとactivatorの2種類のスロットを持ち、ここで登場しているactivatorは以下のプロパティを持っている。

```
{
  on: { [eventName]: eventHandler }
  value: boolean
}
```

このonを受け取り変数onに格納するのが`v-slot:activator="{ on }"`。
ここで受けるonは複数のイベントハンドラを一括で指定できるオブジェクト構文という形で指定している。

詳しくは以下参照
参考）https://qiita.com/aotoriii/items/06ae49c135061a12b75e

# トラブルシュート

## `<v-icon>`で括った文字列がそのまま表示されてしまう場合

おそらくこれで対応可能(vuetifyのversion2.x云々の問題？)
https://qiita.com/tamonmon/items/5656614462241cd00255

以下のパッケージを入れる。

```
front$ npm install @mdi/js file-loader material-design-icons-iconfont --save-dev
```

以下ファイルに追記。

```ts:front/src/plugins/vuetify.ts
import Vue from 'vue';
import Vuetify from 'vuetify/lib';
import 'material-design-icons-iconfont/dist/material-design-icons.css';


Vue.use(Vuetify);

export default new Vuetify({
});
```

上記対応後再度実行すると正常にアイコンが表示される。

> 98% after emitting CopyPlugin

で止まってしまったら一度サーバを再起動してもう一回実行するとうまくいく。