📝

Vue3のscript setupで双方向バインディングを実装する

2022/02/20に公開

最近Vue3を触り始めましたが、Vue2の双方向バインディング .sync は使用できなくなっていました。
https://v3.ja.vuejs.org/guide/migration/v-model.html#v-model

破壊的変更: v-bind の .sync 修飾子とコンポーネントの model オプションは削除され、v-model の引数に置き換えられます。

というわけで、Vue3での記法で動かしてみたのでまとめておきます。

v-modelを利用したバインディング

Vue3では v-model を利用して双方向バインディングを行います。

親コンポーネント

親コンポーネントはシンプルです。
今回は親と子の双方向でバインディングする変数を text としています。
子コンポーネント Child にも、 v-model="text" を指定します。

Parent.vue
<template>
  <p>parent</p>
  <input v-model="text" />

  <!-- 子コンポーネント -->
  <Child v-model="text" />
</template>

<script setup lang="ts">
import { ref } from "vue";
import Child from "@/components/Child.vue";

// 双方向バインディングする変数
const text = ref('');
</script>

子コンポーネント

子コンポーネントは下記のようになります。
親コンポーネントで Child コンポーネントに指定した v-model="text" は、modelValue として受け取ります。

コンポーネント内では、computed プロパティとして利用します。
値に変更があった場合はsetterが呼ばれ、emit を通して親コンポーネントの変数 text が更新されます。

Child.vue
<template>
  <p>child</p>
  <input v-model="text" />
</template>

<script setup lang="ts">
import { computed, defineProps, withDefaults, defineEmits } from "vue";

const props = withDefaults(
  defineProps<{
    modelValue: string;
  }>(),
  {
    modelValue: '' // デフォルト値を指定
  }
);

const emit = defineEmits<{
  (e: 'update:modelValue', text: string): void
}>();

const text = computed({
  get: () => props.modelValue,
  set: (value) => { // 値に変更があると呼ばれるsetter
    emit('update:modelValue', value);
  },
});
</script>

できた!

v-modelに引数を渡したバインディング

v-modelに引数を指定して変数をバインディングすることもできます。
複数の変数をバインディングしたい場合などに利用できます。

親コンポーネント

<Child v-model:text="text" /> のように子コンポーネントに変数を渡します。

Parent.vue
<template>
  <p>parent</p>
  <input v-model="text" />

  <Child v-model:text="text" />
</template>

<script setup lang="ts">
import { ref } from "vue";
import Child from "@/components/Child.vue";

const text = ref('');
</script>

子コンポーネント

子コンポーネントでは text Propsで受け取ります。
v-model に引数を指定した場合は modelValue ではなく引き数名で受け取ることになります。

Child.vue
<template>
  <p>child</p>
  <input v-model="textComputed" />
</template>

<script setup lang="ts">
import { computed, defineProps, withDefaults, defineEmits } from "vue";

const props = withDefaults(
  defineProps<{
    text: string;
  }>(),
  {
    text: ''
  }
);

const emit = defineEmits<{
  (e: 'update:text', text: string): void
}>();

const textComputed = computed({
  get: () => props.text,
  set: (value) => {
    emit('update:text', value);
  },
});
</script>

以上で、「v-modelを利用したバインディング」と同様の動きを実装可能です。

Discussion