我开始了https://laracasts.com/series/learning-vue-step-by-step系列。我在Vue、Laravel和AJAX的课程上停了下来,出现了这个错误:

Vue .js:2574 [Vue警告]:避免直接改变道具,因为每当父组件重新呈现时,该值将被覆盖。相反,应该使用基于道具值的数据或计算属性。道具被突变:"list"(在组件中找到)

我在main.js中有这段代码

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    created() {
        this.list = JSON.parse(this.list);
    }
});
new Vue({
    el: '.container'
})

我知道,当我覆盖列表道具时,问题是在created(),但我是Vue的新手,所以我完全不知道如何修复它。有人知道如何(请解释为什么)解决这个问题吗?


当前回答

Vue模式是向下的道具和向上的事件。这听起来很简单,但在编写定制组件时很容易忘记。

从Vue 2.2.0开始,您可以使用v-model(带有计算属性)。我发现这种组合可以在组件之间创建一个简单、干净和一致的接口:

传递给组件的任何道具都保持响应性(即,它不是克隆的,也不需要监视函数在检测到更改时更新本地副本)。 更改会自动发出到父节点。 可以与多级组件一起使用。

计算属性允许分别定义setter和getter。这允许Task组件被重写如下:

Vue.component('Task', {
    template: '#task-template',
    props: ['list'],
    model: {
        prop: 'list',
        event: 'listchange'
    },
    computed: {
        listLocal: {
            get: function() {
                return this.list
            },
            set: function(value) {
                this.$emit('listchange', value)
            }
        }
    }
})  

model属性定义了哪个道具与v-model相关联,以及在发生更改时将触发哪个事件。然后你可以从父组件中调用这个组件,如下所示:

<Task v-model="parentList"></Task>

listLocal计算属性在组件中提供了一个简单的getter和setter接口(可以把它看作一个私有变量)。在# Task -template中,你可以呈现listLocal,它将保持响应性(即,如果parentList发生变化,它将更新Task组件)。你也可以通过调用setter来改变listLocal(例如this。listLocal = newList),它将把更改发送给父类。

这种模式的优点在于,您可以将listLocal传递给Task的子组件(使用v-model),并且来自子组件的更改将传播到顶层组件。

例如,假设我们有一个单独的EditTask组件,用于对任务数据进行某种类型的修改。通过使用相同的v-model和计算属性模式,我们可以将listLocal传递给组件(使用v-model):

<script type="text/x-template" id="task-template">
    <div>
        <EditTask v-model="listLocal"></EditTask>
    </div>
</script>

如果EditTask发出更改,它将适当地调用listLocal上的set(),从而将事件传播到顶层。类似地,EditTask组件也可以使用v-model调用其他子组件(比如表单元素)。

其他回答

不要直接更改组件中的道具。如果你需要改变它,设置一个新的属性,像这样:

data() {
  return {
    listClone: this.list
  }
}

并更改listClone的值。

答案很简单,您应该通过将值分配给一些局部组件变量(可以是数据属性,用getter、setter或观察者计算)来打破直接的prop突变。

这里有一个使用监视器的简单解决方案。

<template>
  <input
    v-model="input"
    @input="updateInput" 
    @change="updateInput"
  />

</template>

<script>
  export default {
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      input: '',
    };
  },
  watch: {
    value: {
      handler(after) {
        this.input = after;
      },
      immediate: true,
    },
  },
  methods: {
    updateInput() {
      this.$emit('input', this.input);
    },
  },
};
</script>

我用它来创建任何数据输入组件,它工作得很好。从父元素发送的任何新数据(v-model(ed))都将被值监视器监视并分配给输入变量,一旦接收到输入,我们就可以捕捉该操作并向父元素发出输入,表明数据是从表单元素输入的。

Vue只是警告您:您更改了组件中的道具,但当父组件重新呈现时,“list”将被覆盖,您将丢失所有更改。所以这样做是危险的。

使用computed属性,如下所示:

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    computed: {
        listJson: function(){
            return JSON.parse(this.list);
        }
    }
});

道具下降,事件上升。这就是Vue的模式。关键是,如果你试图变异从父母传递的道具。它不会工作,只会被父组件重复覆盖。子组件只能发出一个事件来通知父组件做某事。如果你不喜欢这些限制,你可以使用VUEX(实际上这种模式会吸收复杂的组件结构,你应该使用VUEX!)

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    computed: {
      middleData() {
        return this.list
      }
    },
    watch: {
      list(newVal, oldVal) {
        console.log(newVal)
        this.newList = newVal
      }
    },
    data() {
      return {
        newList: {}
      }
    }
});
new Vue({
    el: '.container'
})

也许这个能满足你的需要。