[Vue3] propsで受け取った値をv-modelにセットする

[Vue3] propsで受け取った値をv-modelにセットする

子コンポーネントのpropsで受け取った値に対する変更を親に反映する方法集です。
サンプルはTypeScriptを使ったComposition APIを使った記述になります。

はじめに

propsで受け取った値を書き換えることは強く推奨されていません。

プリミティブ型(string, number)の場合は、コピーされた値が引数で渡されるので書き換えても親に反映されません。
Vueでも以下のようなエラーがコンソールに出力されます。

runtime-core.esm-bundler.js:218 Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'modelValue'
at _createVNode.onUpdate:modelValue._cache.._cache.

オブジェクト型の場合は、参照が引数で渡されるので書き換えることが出来ますが、それは値がどこで書き換えられているのか収集がつかなくなるため、絶対に辞めるべきでしょう。

※TypeScript向けの型定義を厳密にする defineComponent を使っていると、実行時にエラーとして検出します。
defineComponentを使っていない場合はそのままエラーが表示されず動く場合があるので注意が必要です。

$emitを使ってセットする (template)

Vueには emit という子から親へUpdateイベントを渡す機能が用意されています。
これを使って親で定義した値をセットする処理に移譲します。

// 親コンポーネント ParentComponent
<template>
  <ChildComponent
    :modelValue="name"
    @update:modelValue="setName($event)"
  />
</template>

<script lang="ts">
import { defineComponent } from '@vue/runtime-core';

export default defineComponent({
  setup: () => {
    const name = ref('名前');
    const setName = (value: string) => {
      name.value = value;
    };
    return {
      name,
      setName,
    };
  },
})
</script>

// 子コンポーネント ChildComponent
<template>
  <input type="text"
    :modelValue="modelValue"
    @update:modelValue="$emit('update:modelValue', $event)"
  />
</template>

<script lang="ts">
import { defineComponent } from '@vue/runtime-core';

export default defineComponent({
  props: {
    modelValue: { type: String },
  },
})
</script>

 

$emitを使ってセットする (computed)

v-modelは公式に記載がある通り、modelValueへの受け渡しと同時にupdateイベント時にemitされた値をセットします。

<ChildComponent v-model="pageTitle" />

<!-- これは下記の省略形です -->

<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>

これを利用してcomputedにgetterとsetterを定義し、setter内でemitすることも可能です。
単にセットするだけであれば、1つ目の方法で記載したほうがシンプルで簡潔です。
この方法はsetterとして別の処理も併せて行う必要がある場合に使用すると便利です。

// 親コンポーネント ParentComponent
<template>
  <ChildComponent
    v-model="name"
  />
</template>

<script lang="ts">
import { defineComponent, computed } from '@vue/runtime-core';

export default defineComponent({
  setup: () => {
    const name = ref('名前');
    return { name };
  },
})
</script>

// 子コンポーネント ChildComponent
<template>
  <input type="text"
    v-model="name"
  />
</template>

<script lang="ts">
import { defineComponent } from '@vue/runtime-core';

export default defineComponent({
  props: {
    modelValue: { type: String },
  },
  setup: (props, { emit }) => {
    const name = computed({
      get() {
        return props.modelValue
      },
      set(value: string) {
        emit('updazte:modelValue', value);
      }
    });

    return { name };
  },
})
</script>

 

プログラミングカテゴリの最新記事