16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-01-22

はじめに

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

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

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

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

v-model を使うんでしょう?

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

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 タグにリアルタイムに反映されます。

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

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

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

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>
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 は良いですよ!

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

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>
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 関数でラップすると、なんとコンポーネントから外の変数を操作できてしまいます!

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

素晴らしい!

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

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

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>
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 はいいぞ
  • 関数も渡せるぞ
  • 「とりあえず動かせる」レベル感で使っているので、もっと適切なやり方があるかもしれません。自己責任で
16
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?