Migrating the code of vue2.x to vue3.x, encountered Unexpected mutation of "task" prop in the v-model module

Viewed 70

I used the Composition API when migrating the project from vue2.x to vue3.0, but the page does not work properly, in vue3.0 The environment prompts me to have an "Unexpected mutation of "task" prop" error, I want to know how to write the correct compos API This is Vue2.x code

<template>
  <transition name="fade">
    <div class="task" v-if="!task.deleted">
      <input :id="id" type="checkbox" v-model="task.done" />
      <label :for="id">{{ task.title }}</label>
      <transition name="fade">
        <span
          class="task_delete"
          v-show="task.done"
          @click="deleteTask({ task })"
        >
          <i class="fa fa-trash"></i>
        </span>
      </transition>
    </div>
  </transition>
</template>

<script>
import { mapMutations } from "vuex";
let GID = 1;
export default {
  props: {
    task: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      id: `task-${GID++}`,
    };
  },
  methods: {
    ...mapMutations(["deleteTask"]),
  },
};
</script>

<style lang="scss">
.task {
  display: flex;
  padding: 12px 0;
  border-bottom: 1px solid #eee;
  font-size: 14px;
}
.task input {
  display: none;
}
.task label {
  flex: 1;
  line-height: 20px;
}
.task label:before,
.task label:after {
  content: "";
  display: inline-block;
  margin-right: 20px;
  margin-top: 1px;
  width: 14px;
  height: 14px;
  vertical-align: top;
}
.task label:before {
  border: 1px solid #ccc;
  border-radius: 2px;
  background-color: white;
}
.task label:after {
  content: "\f00c";
  position: relative;
  display: none;
  z-index: 10;
  margin-right: -16px;
  width: 10px;
  height: 10px;
  padding: 3px;
  border-radius: 2px;
  font: normal normal normal 10px/1 FontAwesome;
  color: white;
  background-color: #ccc;
  float: left;
}
.task input:checked + label:after {
  display: inline-block;
}
.task_delete {
  padding: 0 10px;
  color: #ccc;
  font-size: 16px;
}
.fade-leave-to,
.fade-enter {
  opacity: 0;
}
.fade-enter-to,
.fade-leave {
  opacity: 1;
}
.fade-enter-active,
.fade-leave-active {
  transition: all 0.3s ease;
}
</style>

This is Vue3.0 code use Composition API, but not work

<template>
  <transition name="fade">
    <div class="task" v-if="!data.task.deleted">
      <input :id="id" type="checkbox" v-model="data.task.done" />
      <label :for="id">{{ data.task.title }}</label>
      <transition name="fade">
        <span
          class="task_delete"
          v-show="data.task.done"
          @click="deleteTask({ task })"
        >
          <i class="fa fa-trash"></i>
        </span>
      </transition>
    </div>
  </transition>
</template>

<script>
import { reactive } from "vue";
import { mapMutations } from "vuex";

let GID = 1;
export default {
  name: "Task",
  props: {
    task: {
      type: Object,
      required: true,
    },
  },
  setup(props) {
    const data = reactive({
      task: props.task,
      id: `task-${GID++}`,
    });
    return { data };
  },
  methods: {
    ...mapMutations(["deleteTask"]),
  },
};
</script>

<style lang="scss">
.task {
  display: flex;
  padding: 12px 0;
  border-bottom: 1px solid #eee;
  font-size: 14px;
}
.task input {
  display: none;
}
.task label {
  flex: 1;
  line-height: 20px;
}
.task label:before,
.task label:after {
  content: "";
  display: inline-block;
  margin-right: 20px;
  margin-top: 1px;
  width: 14px;
  height: 14px;
  vertical-align: top;
}
.task label:before {
  border: 1px solid #ccc;
  border-radius: 2px;
  background-color: white;
}
.task label:after {
  content: "\f00c";
  position: relative;
  display: none;
  z-index: 10;
  margin-right: -16px;
  width: 10px;
  height: 10px;
  padding: 3px;
  border-radius: 2px;
  font: normal normal normal 10px/1 FontAwesome;
  color: white;
  background-color: #ccc;
  float: left;
}
.task input:checked + label:after {
  display: inline-block;
}
.task_delete {
  padding: 0 10px;
  color: #ccc;
  font-size: 16px;
}
.fade-leave-to,
.fade-enter {
  opacity: 0;
}
.fade-enter-to,
.fade-leave {
  opacity: 1;
}
.fade-enter-active,
.fade-leave-active {
  transition: all 0.3s ease;
}
</style>
1 Answers

I think the problem is you're using v-model for props on checkbox, it will change props value directly and vue not allow it. Try update props value manual by emit event. And u dont need to use props with reactive in child component, but need to reactive that props in parent component

<input :id="id" type="checkbox" v-value="task.done" @change="updateCheckbox($event)")/>  

In script:

export default {
  name: "Task",
  props: {
    task: {
      type: Object,
      required: true,
    },
  },
  emits: ['updateCheckbox'],
  setup(props) {
    const data = reactive({
      id: `task-${GID++}`,
    });
    return { data };
  },
  methods: {
    updateCheckbox(e) {
       this.$emit('updateCheckbox', e.target.value)
    }
  },
};
Related