とりあえずVue3でこんなことしたくなりませんか？

### はじめに

この記事は、Vue3のお作法だとかベストプラクティスだとかを気にする前に、「とりあえずVue3書いてみたいよね！」といった人がリアクティブなコードを書きたくなった時に、まず調べるであろう事柄をちょっとだけピックアップした記事になります。

# 1. フォームの入力内容を変更したら、即座に画面に反映されてほしい

例えばjQueryを使うと、以下のようなコードで実現できます。

```javascript
$(() => {
  // 特定のフォームが変化されたら
  $('#input').change(() => {
    // 値を取得し
    const val = $(this).val();
    // 反映させたい部分に渡す
    $('#output').text(val);
  });
});
```

## `v-model` を使うんでしょう？

「Vue 入力　反映」などと検索すると、**v-model** という単語をたくさん見かけると思います。多分こいつを使うのだろうなと見当を付けて調べ進めると、あるコンポーネントは次のような書き方になるでしょう。

```vue:views/Main.vue
<template>
  <div id="main">
    <input v-model="inputValue" type="text" />
    <p>Current input: {{inputValue}}</p>
  </div>
</template>

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

export default defineComponent({
  name: 'Main',
  setup() {
    return {
      inputValue: ref(''),
    };
  },
});
</script>

<style>
</style>
```

`input` タグを通して入力された内容は `inputValue` 変数に渡され、その下の `p` タグにリアルタイムに反映されます。

良いですね。以上、終わり！

## ちょっと待て。私はカスタムコンポーネントに渡したいんだ

テキストや日付、セレクトボックスなどの入力フォーム一つ一つをカスタムコンポーネントとして定義している場合、以下のようなコードを書いてしまうかもしれません。

```vue:components/SampleForm.vue
<template>
  <div id="sample-form">
    <input v-model="value" type="text" />
  </div>
</template>

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

export default defineComponent({
  name: 'SampleForm',
  props: {
    value: String,
  },
});
</script>

<style>
</style>
```

```vue:views/Main.vue
<template>
  <div id="main">
    <SampleForm :value="inputValue" />
    <p>Current input: {{inputValue}}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
import SampleForm from '@/components/SampleForm.vue';

export default defineComponent({
  name: 'Main',
  components: {
    SampleForm,
  },
  setup() {
    return {
      inputValue: ref(''),
    };
  },
});
</script>

<style>
</style>
```

`SampleForm` に先ほどの `inputValue` を渡し、propsで受け取りコンポーネント内の `input` タグのv-modelとして渡しています。


しかしこれはエラーになります。

```
 ERROR  Failed to compile with 1 error                                                                                                                                                                                   11:16:16 PM

 error  in ./src/components/SampleForm.vue

Module Error (from ./node_modules/eslint-loader/index.js):

/.../src/components/SampleForm.vue
  3:21  error  Unexpected mutation of "value" prop  vue/no-mutating-props

✖ 1 problem (1 error, 0 warnings)
```

どうやらコンポーネントに変数（の可変参照）を渡して操作してもらうことはできないか、一筋縄ではいかないようです。

## `vue-composable` は良いですよ！

先ほどのコード、ちょっと手直しをしてみましょう。

```diff_vue:components/SampleForm.vue
  <template>
    <div id="sample-form">
-     <input v-model="value" type="text" />
+     <input v-model="vmValue" type="text" />
    </div>
  </template>

  <script lang="ts">
  import { defineComponent } from 'vue';
+ import { useVModel } from 'vue-composable';

  export default defineComponent({
    name: 'SampleForm',
    props: {
      value: String,
    },
+   setup (props) {
+     const vmValue = useVModel(props, 'value');
+     return {
+       vmValue,
+     }
+   }
  });
  </script>

  <style>
  </style>
```

```diff_vue:views/Main.vue
  <template>
    <div id="main">
-     <SampleForm :value="inputValue" />
+     <SampleForm v-model:value="inputValue" />
      <p>Current input: {{inputValue}}</p>
    </div>
  </template>

  <script lang="ts">
  import { defineComponent, ref } from 'vue';
  import SampleForm from '@/components/SampleForm.vue';

  export default defineComponent({
    name: 'Main',
    components: {
      SampleForm,
    },
    setup() {
      return {
        inputValue: ref(''),
      };
    },
  });
  </script>

  <style>
  </style>
```

カスタムコンポーネントに渡す際に `v-model:${変数名}="${変数}"` と記述して、カスタムコンポーネントの方では `useVModel` 関数でラップすると、なんとコンポーネントから外の変数を操作できてしまいます！

例えばフォーム全体の入力値を保持するクラスのインスタンスを１つ用意して、各入力項目を表すコンポーネントに対し、対応するインスタンスのフィールドを１つ渡して入力を常に把握するといったことも可能です。

素晴らしい！

# おまけ：こうなったら関数も渡したいね

カスタムコンポーネントがボタンやテキストエリアを持っていて、そのクリックや入力値変更といったイベントを受け取りたい時、次のように書くと良いです。

```diff_vue:views/Main.vue
  <template>
    <div id="main">
      <SampleForm v-model:value="inputValue" />
+     <MyButton @on-click="execute" />
    </div>
  </template>

  <script lang="ts">
  import { defineComponent, ref } from 'vue';
  import SampleForm from '@/components/SampleForm.vue';
+ import MyButton from '@/components/MyButton.vue';

  export default defineComponent({
    name: 'Main',
    components: {
      SampleForm,
+     MyButton,
    },
    setup() {
+     const inputValue = ref('');
+
+     const execute = () => {
+       console.log('execute');
+       console.log(`current input: ${inputValue.value}`);
+     };
+
     return {
-       inputValue: ref(''),
+       inputValue,
+       execute,
      };
    },
  });
</script>

<style>
</style>
```

```vue:components/MyButton.vue
<template>
  <div id="my-button">
    <button @click="onClick">ボタン</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, SetupContext } from 'vue';

export default defineComponent({
  name: 'SampleForm',
  setup (_props, context: SetupContext) {
    const onClick = () => {
      context.emit('on-click');
    };

    return {
      vmValue,
      onClick,
    }
  }
});
</script>

<style>
</style>
```

コンポーネント呼び出しの際に `@event-name` を記述してハンドラを渡し、コンポーネント側では `context.emit('event-name')` で実行することが可能です。

汎用的に使えそうですね！

### まとめ

- `vue-composable` はいいぞ
- 関数も渡せるぞ
- 「とりあえず動かせる」レベル感で使っているので、もっと適切なやり方があるかもしれません。自己責任で