📚

【Vue3】Props をオブジェクトや配列として受け取って一部変更すると、親コンポーネントのデータも同時に変更される

2022/12/30に公開

はじめに

Vue において props は readOnly = 変更不可とされています。
子コンポーネントから親コンポーネントのデータを書き換えるためには、emit を使う必要があるとのことです。

https://ja.vuejs.org/guide/components/props.html#one-way-data-flow

しかし、props が配列やオブジェクトの場合、子コンポーネントのプロパティを変更するだけで emit を使わずとも親コンポーネントの値が変更されてしまいます。

環境

  • Google Chrome
  • M1 Mac
  • Vue3, Vite
  • TypeScript, JavaScript 両方で確認

変更される条件

  • Props をオブジェクト、あるいは配列として定義し、子コンポーネントで受け取る
  • オブジェクトの一つのプロパティ (今回の場合は firstname) を変更する
App.vue
<script setup lang="ts">
import { ref } from 'vue';
import ChildComponent from './components/ChildComponent.vue';

let user = ref({
  firstname: 'Taro',
  lastname: 'Sato',
});
</script>

<template>
  <ChildComponent :user="user" />
  親コンポーネントの中身: {{ user.firstname }}
</template>

components/ChildComponent.vue
<script setup lang="ts">
import { computed, ref } from 'vue';
const props = defineProps({
  user: Object,
});

// ref()に props の値を入れると、初期値を props の値にできる
const name = ref(props.user);
const firstname = ref(props.user.firstname);

const changeFirstname = function () {
  name.value.firstname = 'Hanako';
};
</script>

<template>
  <div>
    <span>オブジェクトの一部として変更: </span>
    <input type="text" v-model="name.firstname" />
  </div>
  <div>
    <span>プリミティブ値として変更: </span>
    <input type="text" v-model="firstname" />
  </div>
  <div>
    <span>関数からオブジェクトの一部を変更: </span>
    <button @click="changeFirstname">firstname を変える</button>
  </div>
  <div>子コンポーネントのprops の値: {{ user.firstname }}</div>
</template>


子コンポーネントを変更すると親コンポーネントの値も変更されています。

終わりに

僕はこの仕様にぶつかった時に「なんでこうなるんだ……?」となって数時間溶かしてしまったので、同じようにぶつかった人を救うためにこの記事を書きました。

解決した後に適切な言葉で検索したところ、この記事の内容は公式 doc に書いてありました。なんだと……。

https://ja.vuejs.org/guide/components/props.html#mutating-object-array-props

多くの場合でベストプラクティスではないので避けるべし、とのことです。

Discussion