Why my Vue Child Component have already assign prop to $data at first, but still accidentally mutate the Parent $data?

Viewed 33

Please see this minimum example

App.vue

<template>
  <EditPerseon :previousPerson="persons[0]" />
</template>

<script>
  import EditPerseon from "./EditPerseon.vue";

  export default {
    data() {
      return {
        persons: [
          { name: "Bob", age: 18 },
          { name: "Amy", age: 20 },
        ],
      };
    },
    watch: {
      $data: {
        handler() {
          console.log("My $data have been mutated!");
        },
        deep: true,
      },
    },
    components: {
      EditPerseon,
    },
  };
</script>

EditPerseon.vue

<template>
  <input v-model="currentPerson.name" />
</template>

<script>
  export default {
    props: {
      previousPerson: Object,
    },
    data() {
      return {
        currentPerson: this.previousPerson,
      };
    },
  };
</script>

Also on CodeSandbox:

https://codesandbox.io/s/why-my-vue-child-component-have-already-assign-prop-to-data-at-first-but-still-accidentaly-mutate-the-parent-data-fueuw?fontsize=14&hidenavigation=1&theme=dark

EditPerseon.vue is my child component, but it still accidentally mutate my parent's $data.

Why is this happening?

How can I prevent this?

2 Answers

When you define an object (or an array) assigning it to another object in javaScript, you end up with two references to the same object. I think you believe that your line:

currentPerson: this.previousPerson,

is somehow creating a new object currentPerson that copies the values from this.previousPerson. However, what it's happening is that now you have two different variables that both are linked with the original object: so changes to currentPerson will actually modify the parent's person[0].

One thing you can do to mitigate this is to forge a new object in the child component:

    data() {
      return {
        currentPerson: {...this.previousPerson},
      };
    },

or:

    data() {
      return {
        currentPerson: {
          name: this.previousPerson.name
        },
      };
    },

The first option will fail if some fields of the object are objects themselves. The second one is safer but more verbose. You may try some packages like cloneDeep to safely clone objects.

A good general resource to get more info about the deal with object references and copies: https://javascript.info/object-copy.

Apart from this, I believe watcher handlers always trigger at created, so the handler will fire even if no change is detected (but I'm not 100% sure about this atm).

Because objects passes by reference. When you change to current person actually you change the original one in the memory.Because of that vue gives warning for that. You need to copy to prevent this.

 data() {
  return {
    currentPerson: {...this.previousPerson},
  };
},
Related