Vue.js を使ってみたの続き

# はじめに

いわゆるウェブアプリを作るのに自分は Vue.js を使っています。

[Vue.js を使ってみた - Qiita](https://qiita.com/tinymouse/items/d3eceea8d0b4237221f5)
[Onsen UI＋Vue.js を使ってみた - Qiita](https://qiita.com/tinymouse/items/82cce234e4b6fc0f17c7)

ただし、Vue 3 を使っていませんでした。また、コンポーネントも使っていませんでした。
今後の開発に使うかどうか分かりませんが、勉強しておこうと思いました。

# Vue.js を使ってみた

・Vue.2.7
・Vue 3.3

## インストールする

Vue.js のアプリを作るには、Node.js を使ってローカルな開発環境を構築するのが一般的なようです。

[Vue.jsのインストール → プロジェクト作成と起動まで～ - Qiita](https://qiita.com/C_HERO/items/318fe65871f8e53e8c80)

今回はあくまでも HTML ファイル一つで、ブラウザだけで動く Vue.js アプリを作っていきたいと思います。

HTML ファイルに以下を追記します。

```html:
    <script src="https://unpkg.com/vue@2.7.10/dist/vue.min.js"></script>
```

Vue 3 のとき↓

```html:
    <script src="https://unpkg.com/vue@3.3.7/dist/vue.global.prod.js"></script>
```

## マウントする

```Vue``` オブジェクトで管理する対象になる HTML タグを指定するのに、オプション指定の ```el``` プロパティを使います。↓

```html:
    <div id='root'>
      <p>Hello, {{ name }}.</p>
    </div>
```
```javascript:
<script>
    var name = "Vue.js";

    new Vue({
      el: '#root',
      data: {
        name: name
      }
    });
```

```Vue``` オブジェクトを生成しておいて、HTML タグに ```mount``` する書き方があります。↓

参考：[マウントのしかた まとめ - Qiita](https://qiita.com/kazutosato/items/60527d0876ef13ab96cf)

```javascript:
<script>
    var name = "Vue.js";

    var vm = new Vue({
      data: {
        name: name
      }
    });
    vm.$mount('#root');
```

Vue 3 の書き方↓

参考：[Vue2 Vue3 比較（new Vue() vs createApp()）- Qiita](https://qiita.com/zixuan/items/861128104646fce9badc)

```javascript:
    let name = "Vue.js";

    const app = Vue.createApp({
      data() {
        return {
          name: name
        };
      } 
    });
    app.mount('#root');
```

```data``` の書き方を変えないといけません。↑

## コンポーネントを作成する

Vue.js も React.js のように、コンポーネントを作って利用することができます。

参考：[コンポーネントの基本 — Vue.js](https://v2.ja.vuejs.org/v2/guide/components)

```javascript:
<script>
    Vue.component('v-element', {
      props: [ 'name' ],
      template: `<p>Hello, {{ name }}.</p>`
    });

    new Vue({
      el: '#root'
    });
```
```html:
    <div id='root'>
      <v-element name="Vue.js" />
    </div>
```

独自タグを作れると言ってよさそうです。↑
呼出元から値を渡してコンポーネントで受取して使用できます。

HTML タグはマウントするポイントだけにして、Vue オブジェクトの ```template``` に内容を記述してマウントする書き方もできます。

```html:
    <div id='root' />
```
```javascript:
<script>
    var vm = new Vue({
      template: `<v-element name="Vue.js" />`
    });
    
    Vue.component('v-element', {
      props: [ 'name' ],
      template: `<p>Hello, {{ name }}.</p>`
    });

    vm.$mount('#root');
```

Vue 3 のとき↓

参考：[【vue3】component基礎 #JavaScript - Qiita](https://qiita.com/Quest_love33/items/a0d8a105ef3c0854b5ff)

```javascript:
<script>
    const app = Vue.createApp({
      template: '<v-element name="Vue.js" />'
    });

    app.component('v-element', {
      props: [ 'name' ],
      template: `<p>Hello, {{ name }}.</p>`
    });

    app.mount('#root');
```

## コンポーネントに状態を加える

コンポーネントは、呼出元から値を受取って使うだけでなく、内部で値を保持して使用します。
```data``` と ```methods``` を用意します。

カウントアップするボタンのコンポーネントを作ってみます。↓

```javascript:
    Vue.component('v-element', {
      data() {
        return {
          count: 0
        }
      },
      methods: {
        addCount() {
          this.count += 1;
        }
      },
      template: `
        <div>
          <p>count: {{ count }}</p>
          <button v-on:click='addCount'>＋</button>
        </div>
      `
    });
```

Vue 3 の書き方↓

```javascript:
    app.component('v-element', {
      data() {
        return {
          count: 0
        }
      },
      methods: {
        addCount() {
          this.count += 1;
        }
      },
      template: `
        <div>
          <p>count: {{ count }}</p>
          <button v-on:click='addCount'>＋</button>
        </div>
      `
    });
```

## コンポーネント間で状態を共有する①

親のコンポーネントで定義された変数を、埋込された子のコンポーネントで参照して使用したいことがあります。

参考：[Vue.jsコンポーネント間でデータをやり取りする方法](https://zenn.dev/chiyopu/articles/fd77f09f8ee57e)

子コンポーネントで ```props``` を定義します。親コンポーネントから ```v-bind``` で子コンポーネントに値を渡せるようになります。

```javascript:
    Vue.component('v-child', {
      props: [ 'count' ],
      template: `
        <div>
          <p>Child</p>
          <p>count: {{ count }}</p>
        </div>
      `
    });
```
```javascript:
    Vue.component('v-parent', {
      data() {
        return {
          count: 0
        }
      },
      methods: {
        addCount(){
          this.count += 1;
        }
      },
      template: `
        <div>
          <p>Parent</p>
          <p>count: {{ count }}</p>
          <button v-on:click='addCount'>＋</button>
          <v-child v-bind:count='count' />
        </div>
      `
    });
```

Vue 3 の書き方↓

```javascript:
    app.component('v-child', {
      props: [ 'count' ],
      template: `
        <div>
          <p>Child</p>
          <p>count: {{ count }}</p>
        </div>
      `
    });
```
```javascript:
    app.component('v-parent', {
      data() {
        return {
          count: 0
        }
      },
      methods: {
        addCount() {
          this.count += 1;
        }
      },
      template: `
        <div>
          <p>Parent</p>
          <p>count: {{ count }}</p>
          <button v-on:click='addCount'>＋</button>
          <v-child v-bind:count='count' />
        </div>
      `
    });
```

親コンポーネントで値を変えると子コンポーネントの値も変わります。

## コンポーネント間で状態を共有する②

上記の例は、子のコンポーネントで値を変えても、親のコンポーネントの値は変わりません。
親コンポーネントで定義された変数を、子コンポーネントで参照して変更して、それを親コンポーネントで使用したいことがあります。

参考：[Vue.jsコンポーネント間でデータをやり取りする方法](https://zenn.dev/chiyopu/articles/fd77f09f8ee57e)

親コンポーネントに値を渡したいタイミングで、子コンポーネントでイベントを ```emit``` します。親コンポーネントは ```v-on``` でイベントを受取して、そこでメソッドを呼出して値を受取できます。

```javascript:
    Vue.component('v-child', {
      props: [ 'count' ],
      methods: {
        addCount() {
          this.count += 1;
          this.$emit('CountChanged', this.count);
        }
      },
      template: `
        <div>
          <p>Child</p>
          <p>count: {{ count }}</p>
          <button v-on:click='addCount'>＋</button>
        </div>
      `
    });
```
```javascript:
    Vue.component('v-parent', {
      中略
      methods: {
        中略
        countChanged(value) {
          this.count = value;
        }
      },
      template: `
        <div>
          <p>Parent</p>
          <p>count: {{ count }}</p>
          中略
          <v-child v-bind:count='count' v-on:CountChanged='countChanged' />
        </div>
      `
    });
```

Vue 3 の書き方↓

参考：[emits オプション | Vue 3 移行ガイド](https://v3-migration.vuejs.org/ja/breaking-changes/emits-option.html)

```javascript:
    app.component('v-child', {
      props: [ 'count' ],
      emits: [ 'CountChanged' ],
      setup(props, context) {
        return {
          addCount() {
            props.count += 1;
            context.emit('CountChanged', props.count);
          }
        }
      },
      template: `
        <div>
          <p>Child</p>
          <p>count: {{ count }}</p>
          <button v-on:click='addCount'>＋</button>
        </div>
      `
    });
```

```javascript:
    app.component('v-parent', {
      中略
      methods: {
        中略
        countChanged(value) {
          this.count = value;
        }
      },
      template: `
        <div>
          <p>Parent</p>
          <p>count: {{ count }}</p>
          中略
          <v-child v-bind:count='count' v-on:CountChanged='countChanged' />
        </div>
      `
    });
```

子コンポーネントで値を変えると親コンポーネントの値も変わります。

## コンポーネント間で状態を共有する③

```html:
          <v-child v-bind:count='count' v-on:CountChanged='countChanged' />
```

```v-on``` と ```v-bind``` を ```v-bind``` に書換してみます。

```html:
          <v-child v-model='count' />
```

Vue 2 の場合、これは以下と同じです。

```html:
          <v-child v-bind:value='count' v-on:input='count = $event.target.value' />
```

参考：[v-model を正しく理解して親子間コンポーネントのデータ伝播をマスターする - Qiita](https://qiita.com/toshifumiimanishi/items/07c8e96eb6f8e50c5e27)

これに合わせて子コンポーネントの ```props``` と ```emit``` を書換します。

```javascript:
    Vue.component('v-child', {
      props: [ 'value' ],
      methods: {
        addCount() {
          this.value += 1;
          this.$emit('input', this.value);
        }
      },
      以下略
```

Vue 3 の書き方↓

参考：[v-model | Vue 3 移行ガイド](https://v3-migration.vuejs.org/ja/breaking-changes/v-model.html)

```v-model``` に引数を指定できるので、引数 ```count``` で子に渡すようにします。子で ```emit``` するイベントは、引数 ```count``` を指定して ```update``` します。

```html:
          <v-child v-model:count='count' />
```
```javascript:
    app.component('v-child', {
      props: [ 'count' ],
      emits: [ 'update' ],
      setup(props, context) {
        return {
          addCount() {
            props.count += 1;
            context.emit('update:count', props.count);
          }
        }
      },
      以下略
```

## コンポーネント間で状態を共有する④

コンポーネント間で状態を共有する、別の方法です。

参考：[Vue.jsでコンポーネント間のデータ受け渡し(Provide／Inject) - Qiita](https://qiita.com/SeiuchiAzarashi/items/4ce49fcd89f02a8403a9)

親コンポーネントで ```provide``` して、子コンポーネントで ```inject``` します。

```javascript:
    Vue.component('v-parent', {
      data() {
        return {
          count: 0
        }
      },
      provide() {
        return {
          count: Vue.computed(() => this.count),
          updateCount: (value) => this.count = value,
        };
      },
      以下略

    Vue.component('v-child', {
      inject: [ 'count', 'updateCount' ],
      methods: {
        addCount() {
          this.updateCount(this.count + 1);
        }
      },
      以下略
```

Vue 3 の書き方↓

```javascript:
    app.component('v-parent', {
      data() {
        return {
          count: 0
        }
      },
      provide() {
        return {
          count: Vue.computed(() => this.count),
          updateCount: (value) => this.count = value,
        };
      },
      以下略

    app.component('v-child', {
      inject: [ 'count', 'updateCount' ],
      methods: {
        addCount() {
          this.updateCount(this.count + 1);
        }
      },
      以下略
```

親コンポーネントで用意した変数 ```count``` に対して、これを更新するメソッド ```updateCount``` と一緒に ```provide``` します。この変数とメソッドを、子コンポーネントで ```inject``` して利用できるようにします。子コンポーネントで ```updateCount``` を使って変数 ```count``` を更新すると、親コンポーネントの ```count``` が更新されます。