とりあえずHTML直書きをやめるためのVue3入門①：基本編

:::note
基本編（コンポーネントの作り方）です。
この記事を読めば「膨大な`index.html`」から解放されます
:::


:::note warn
命名規則（スネークケース、ケバブケース、キャメルケースの使い分け）がバックエンド言語に寄せていまして、推奨とはかなり違います。動作は問題ありません。
https://ja.vuejs.org/style-guide/rules-strongly-recommended.html
:::

## はじめに
### HTML直書き、めんどくさいですよね
フロントエンド初心者のみなさん、HTML直書き楽しんでますか？？
楽しんでないですよね(断定)

**ページが大きくなればなるほど辛くなってきますよね**

なぜ辛くなるのか当てましょう。

* 全てを1ファイルに書き、内容が膨れ上がるほどだんだん視認性・メンテナンス性が悪くなるから
* 同じような要素を再利用できないから
(`template`を使うというのもありますが)
* 変数が多くなればなるほど、更新時のコールバックなりのやりとりが多くなるから

当たりですか？
もう、苦しむのはやめませんか？やめましょう

### Vueを使おう
`Vue`を使いましょう。`Vue`はいいものです。 ~~こら、`React`の方を向くんじゃない。今から`Vue`をするんや。~~

`Vue`は先述した3論点を全て解決します。
すなわち、
* **部品ごとにHTMLもJavaScriptもCSSもまとめてファイルを分けられるため、視認性がとても良い**
* **同じ部品を気楽に再利用でき、なんなら`for`文まで用意されている**
* **変数の更新時、依存したデータ自動で更新させることができる**

ということができます。素敵ですね。

フレームワークは覚えること多いってよく聞きますよね？大丈夫！
今回は
* **テキトーに！とりあえず`Vue`を扱えるように！**
* **環境構築は簡単(というか無し)に！**

簡略化してお届けします。

## 使い方
### 環境構築
`VSCode`をインストールして、拡張機能に
* HTMLをリアルタイムでブラウザに映す`live server`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/2168465d-c320-51e1-914f-2360b2412984.png)
* Vueの記入がやりやすくなる`Vue Language Features (Volar)`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/3c10b98e-a864-c133-2c00-2fb93a50fdb5.png)


の2つを入れてください。終わり。

:::note
`npm`? やりたかったらやってください。
:::

### 理解不要テンプレート
こちらのレポジトリのディレクトリ構成でそのまま動きます：https://github.com/konbraphat51/Vue3_CDN_template

こんなディレクトリ構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/65842085-1a66-e687-3fff-305331a93a42.png)

こちらにもスクリプトを掲載します
メインのHTMLとなる`index.html`、**理解不要**
```html: index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>TITLE</title>

    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://unpkg.com/vue-i18n@9"></script>
    <script src="https://unpkg.com/vue-router@4.0.15/dist/vue-router.global.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader/dist/vue3-sfc-loader.js"></script>
</head>

<body>
    <div id="app"></div>

    <script>
        //initialize i18n
        const i18n = VueI18n.createI18n({
            legacy: false,
            locale: navigator.language,
            fallbackLocale: "en",
        });

        //initialize vue3-sfc-loader
        const options = {
            moduleCache: {
                vue: Vue
            },

            //read subdirectory vue component file.
            async getFile(url) {

                const res = await fetch(url);
                if (!res.ok)
                    throw Object.assign(new Error(res.statusText + ' ' + url), { res });
                return {
                    getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
                }
            },

            addStyle(textContent) {

                const style = Object.assign(document.createElement('style'), { textContent });
                const ref = document.head.getElementsByTagName('style')[0] || null;
                document.head.insertBefore(style, ref);
            },


            //read <i18n> tag
            customBlockHandler(block, filename, options) {
                if (block.type !== 'i18n')
                    return
                const messages = JSON.parse(block.content);
                for (let locale in messages)
                    i18n.global.mergeLocaleMessage(locale, messages[locale]);
            }
        }
        const { loadModule } = window["vue3-sfc-loader"];

        //Mount App.vue
        const app = Vue.createApp(
            Vue.defineAsyncComponent(
                () => loadModule("./src/components/App.vue", options)
            ),
        )
        app.use(i18n);
    </script>
    <script src="./src/scripts/router.js"></script>
    <script>
        app.use(router)
        app.mount('#app');
    </script>
</body>

</html>
```

特に変更しなくとも`Vue`は動作します。
ページタイトル変えたいときなどはどうぞ編集してください。

別にこれが何をするhtmlか理解しなくとも大丈夫なのですが、気になる人のために。
めっちゃ簡単に言えば、
* ネット上から`Vue`ライブラリを引っ張ってきて（**CDN**といいます）、
* 後述する**コンポーネントを`div(id="app")`にマウント（ひっつけ）しています**

いろいろスクリプトが長いのは、`vue3-sfc-loader`のセットアップをしています。ファイル分けする場合Vue CDNは本来使うものではないですが、無理やり可能にしています。

こちらが我々が今から操作する**コンポーネント**のテンプレート

```vue: TEMPLATE
<template>
    <!-- <div id="COMPONENT_NAME">
        any HTML you want

        <button id="example" @click="select('example')">{{ t("Example.button") }}</button>

        <SUB_COMPONENT :sub_component_props_name:"example_parameter" @event_name="function_name()"/>

        <SOMETHING v-for="example in example_list" :key="example.id" :example_props_name="example">
    </div> -->
</template>

<script>
export default Vue.defineComponent({
    name: 'COMOPNENT_NAME',
    components: {
        //"SUB_COMPONENT": Vue.defineAsyncComponent(() => loadModule("src/components/SUB_COMPONENT.vue", options)),
    },
    setup() {
        //一番最初に走るjavascript

        //i18n設定
        const { t } = VueI18n.useI18n()
        return { t }
    },
    mounted() {
        //マウントされたときに走るjavascript。setup()より基本的にこれを使う。
    },
    updated() {
        //表示内容が更新されたときに走るjavascript
    },
    data() {
        //プライベート変数
        return {
            //example_variable: "example"
        }
    },
    props: {
        //コンポーネントの引数

        // example_parameter: {
        //     type: String,
        //     required: true
        // }
    },
    methods: {
        // このコンポーネントのメソッド
        // select(example) {
        //     this.$emit("onSelected", example)
        // },
        // example_method() {
        //   
        // }
    },
    watch: {
        // 変化監視
        // example_variable: {
        //     handler: function (newVal, oldVal) {
        //         //script called when example_variable is changed
        //     },
        //     deep: true
        // }
    },
    computed: {
        // プライベート変数の一種。
        // 中身の計算に使われている変数が更新されると、これを検知してこちらも更新される。
        // example_computed() {
        //      
        //     return this.example_variable + " computed"
        // }
    }
})
</script>

<i18n>
    {
        "en": {
            "Example": {
                "button": "example"
            }
        },
        "ja": {
            "Example": {
                "button": "例"
            }
        }
    }
</i18n>

<style>
/*.COMPONENT_NAME {
    font-size: 1.5em;
    font-weight: bold;     
}*/
</style>
```

これ見てヴゥエってなりましたか？ 大丈夫です。**このテンプレートをコピペして、お好きな箇所を付け足すだけで** 大丈夫です。
**難しそうに見えて、頭空っぽでできます**

### コンポーネントとは？
簡単に言うと **「部品」** であり、**自作HTMLタグ** ともいえます。

例えば、標準HTMLでは`<button>`と書けば、「クリックするとイベントを発し、四角い枠で囲まれた文字列」がセットで設置されますよね？
こんなタグが作れます。例えば「クリックすると振動する説明文タグ」でもみたいな誰得HTMLタグでも。
例えば`UserList`というコンポーネントを作れば、`<UserList />`というタグを書けるようできるわけです。

これを使うことで、 **デザインの再利用も簡単** になりますね。
そして、コンポーネントごとにファイルを作るので、 **ファイルがかさばらずに、見たいところだけ見えて視認性が良く** なりますね。

### コンポーネントの作り方
まずは、拡張子が`.vue`のファイルを良い感じのところに置いてください。慣習的には、`src/components`ディレクトリに設置しますが、どこでもいいです。

上記`TEMPLATE.vue`の中身をコピペしてください。

そして、下記の2点の操作をしてください：
* `<template>`の中身を、欲しい`HTML`に書き換えてください。
* `<script>`の中、`defineComponent`の中、`name`を欲しいコンポーネント名にしてください。

これでコンポーネントの完成です。

ここでは例として、こんな例を用意しました。（不要な箇所は省略していますが、あってもいいです）
```vue: Tamago.vue
<template>
    <div id="Tamago">
        Tamago
    </div>
</template>

<script>
    export default Vue.defineComponent({
        name: 'Tamago',
    })
</script>
```

### コンポーネントを表示させる
ドラクエでは装備を持つだけでは効果がないのと同じで、コンポーネントも作るだけでは意味がありません。（表示されません）
**表示させるには、`App`コンポーネントに追加させる必要があります**

`App.vue`をこのように編集（不要な箇所は削除していますが、あってもいいです）
```diff_vue: App.vue
<template>
    <div id="app">
+        <Tamago />
    </div>
</template>

<script>
export default Vue.defineComponent({
    name: 'App',
    components: {
+        "Tamago": Vue.defineAsyncComponent(() => loadModule("src/components/Tamago.vue", options)),
    },
})
</script>
```
`loadModule()`のパスに注意。
`options`はグローバル変数なので思考停止で書いてください。

表示されているはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/5f32e085-3e83-6262-51e4-81694cf39d1c.png)

ちなみに、コンソールがこのような感じで表示されますが、健全です。（無理してファイル分けしているのでね。しかし全然問題ありません）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/a5add048-4574-a1b4-e462-dffb103ece1b.png)

重要なところは3点。
* `App.vue`の`<template>`は**ページ全体の**HTML。
  `index.html`をそのままにしていたら、ですけど。
* コンポーネントAにコンポーネントBを取り込むには、`components`に
  `"Tamago": Vue.defineAsyncComponent(() => loadModule("src/components/Tamago.vue", options))`
  を追加する。
  **コピペ呪文で大丈夫です。** キー名をコンポーネント名に、パスはもちろん`.vue`ファイルの場所、`options`はグローバル変数です。
* コンポーネントを作れば、`<(コンポーネント名) />`タグを使える
最後の`/`に注意してください。

#### コンポーネントにスタイルを付加する
もうお気づきかとおもいますけど、`<styles>`に **そのまま`CSS`を書けば大丈夫です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/84e58173-67e0-c50b-30a8-f69c580b2c4b.png)

:::note
`Copilot`を使っていれば、普通にCSS書くよりもかなり当意即妙のスタイリングをしてくれます。
例えば、このサイトのCSSはほぼ全て`Copilot`が書いてくれました：
https://konbraphat51.github.io/HSS_examples/
:::

## 今回はここまで
### 今あなたができるようになったこと
超簡単に、HTMLを部品に分割することができるようになりました。
デカい`index.html`とはもうおさらばです。
これからは、積極的にwebページをコンポーネントに分解し、気持ちよく、頭空っぽでページを作りましょう。
**`Vue`最高！！**

### 次回予告
テンプレートにある通り、まだまだ機能がいっぱいあります。
次回は、**変数・データのコンポーネント間の受け渡し**を扱います。

:::note
いいね頂けると泣きながら喜びます><
:::