Vue 2 -变种道具Vue -warn

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

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

我在main.js中有这个代码

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

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

319126 次浏览

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

使用computed属性,如下所示:

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

这与在Vue 2中局部突变prop被认为是反模式

如果你想要局部变异一个道具,你现在应该做的是在你的data中声明一个字段,使用props值作为它的初始值,然后改变副本:

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

你可以在Vue.js官方指南上阅读更多


请注意你应该not为你的__ABC0和data使用相同的名称,即:

data: function () { return { list: JSON.parse(this.list) } } // WRONG!!

由于我觉得有些混乱关于props反应性,我建议你看一下线程

Vue.js认为这是一个反模式。例如,声明和设置一些道具

this.propsVal = 'new Props Value'

所以为了解决这个问题,你必须从数据或Vue实例的计算属性的道具中获取一个值,就像这样:

props: ['propsVal'],
data: function() {
return {
propVal: this.propsVal
};
},
methods: {
...
}

这肯定有用。

如果你想改变道具-使用对象。

<component :model="global.price"></component>

组件:

props: ['model'],
methods: {
changeValue: function() {
this.model.value = "new value";
}
}
我也遇到过这个问题。警告消失后,我使用$on$emit。 类似于使用$on$emit将数据从子组件发送到父组件

如果您正在使用Lodash,您可以在返回之前克隆道具。如果同时修改父节点和子节点上的道具,则此模式非常有用。

假设组件网格上有道具列表

在父组件中

<grid :list.sync="list"></grid>

在子组件中

props: ['list'],
methods:{
doSomethingOnClick(entry){
let modifiedList = _.clone(this.list)
modifiedList = _.uniq(modifiedList) // Removes duplicates
this.$emit('update:list', modifiedList)
}
}

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

你不应该在子组件中改变props的值。 如果你真的需要改变它,你可以使用.sync。 就像这样

<your-component :list.sync="list"></your-component>

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

您需要像这样添加computed方法

component.vue

props: ['list'],
computed: {
listJson: function(){
return JSON.parse(this.list);
}
}

Vue.js的道具不能被改变,因为这被认为是Vue中的反模式。

您需要采取的方法是在组件上创建一个数据属性,该属性引用列表的原始道具属性

props: ['list'],
data: () {
return {
parsedList: JSON.parse(this.list)
}
}

现在传递给组件的列表结构通过组件的data属性被引用和改变:-)

如果你想做的不仅仅是解析你的list属性,那么使用Vue组件的computed属性。 这允许你对你的道具进行更深入的突变
props: ['list'],
computed: {
filteredJSONList: () => {
let parsedList = JSON.parse(this.list)
let filteredList = parsedList.filter(listItem => listItem.active)
console.log(filteredList)
return filteredList
}
}

上面的例子解析你的列表道具,并将其过滤到只有活跃的 list-tems,将其记录为为了搞笑和傻笑并返回它。

请注意: both data &computed属性在模板中被引用

<pre>\{\{parsedList}}</pre>


<pre>\{\{filteredJSONList}}</pre>

很容易认为computed属性(作为一个方法)需要被调用…它不

单向数据流, 根据https://v2.vuejs.org/v2/guide/components.html,组件遵循单向 数据流, 所有的道具在子属性和父属性之间形成了一个单向的向下绑定,当父属性更新时,它将向下流到子属性,而不是相反,这可以防止子组件意外地改变父属性,这可能会使你的应用程序的数据流更难理解 此外,每次父组件更新所有的道具 子组件中的值将被刷新为最新值。这意味着您不应该试图改变子组件中的道具。如果你这样做了,vue会在 控制台。< / p > 通常有两种情况很容易对道具进行变异: prop用于传入一个初始值;子组件希望随后将其用作本地数据属性。 道具作为需要转换的原始值传递进来。 这些用例的正确答案是: 定义一个本地数据属性,使用prop的初始值作为其初始值:

props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}

定义一个计算属性,从prop的值计算:

props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
根据VueJs 2.0,你不应该改变组件内部的道具。它们只会被父母变异。因此,您应该在数据中定义不同名称的变量,并通过观察实际的道具来更新它们。 如果列表道具被父元素更改,您可以解析它并将其分配给mutableList。这是一个完整的解决方案
Vue.component('task', {
template: ´<ul>
<li v-for="item in mutableList">
\{\{item.name}}
</li>
</ul>´,
props: ['list'],
data: function () {
return {
mutableList = JSON.parse(this.list);
}
},
watch:{
list: function(){
this.mutableList = JSON.parse(this.list);
}
}
});

它使用mutableelist来呈现模板,这样就可以在组件中保证列表道具的安全。

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

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

并更改listClone的值。

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

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

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

计算属性允许分别定义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)
}
}
}
})

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

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

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

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

例如,我们有一个单独的EditTask组件,用于对任务数据进行某种类型的修改。通过使用相同的v-model和computed属性模式,我们可以将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调用其他子组件(比如表单元素)。

除上述问题外,如有下列问题:

"如果props值不是必需的,因此不总是返回,传递的数据将返回undefined(而不是空)"。这可能会扰乱<select>的默认值,我通过检查该值是否设置在beforeMount()(如果没有设置它)来解决它:

< em > JS: < / em >

export default {
name: 'user_register',
data: () => ({
oldDobMonthMutated: this.oldDobMonth,
}),
props: [
'oldDobMonth',
'dobMonths', //Used for the select loop
],
beforeMount() {
if (!this.oldDobMonth) {
this.oldDobMonthMutated = '';
} else {
this.oldDobMonthMutated = this.oldDobMonth
}
}
}

Html: < em > < / em >

<select v-model="oldDobMonthMutated" id="dob_months" name="dob_month">


<option selected="selected" disabled="disabled" hidden="hidden" value="">
Select Month
</option>


<option v-for="dobMonth in dobMonths"
:key="dobMonth.dob_month_slug"
:value="dobMonth.dob_month_slug">
\{\{ dobMonth.dob_month_name }}
</option>


</select>
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'
})

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

再加上最好的答案,

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

通过数组设置道具是为了开发/原型,在生产中确保设置道具类型(https://v2.vuejs.org/v2/guide/components-props.html),并设置一个默认值,以防道具没有被父元素填充。

Vue.component('task', {
template: '#task-template',
props: {
list: {
type: String,
default() {
return '{}'
}
}
},
data: function () {
return {
mutableList: JSON.parse(this.list);
}
}
});

这样你至少在mutableList中得到一个空对象,而不是一个JSON。如果未定义,则解析错误。

我想给出这个答案,这有助于避免使用大量的代码,观察者和计算属性。在某些情况下,这可能是一个很好的解决方案:

道具是用来提供单向交流的。

当你有一个带有道具的模态show/hide按钮时,对我来说最好的解决方案是发出一个事件:

<button @click="$emit('close')">Close Modal</button>

然后添加监听器到模态元素:

<modal :show="show" @close="show = false"></modal>

(在这种情况下,道具show可能是不必要的,因为你可以直接在基本模式上使用简单的v-if="show")

我个人总是建议,如果你需要改变道具,首先将它们传递给计算属性,然后从那里返回,然后就可以很容易地改变道具,即使在那你也可以跟踪道具的突变,如果它们是从另一个组件中突变的,或者我们也可以观察。

因为Vue道具是数据流的一种方式,这可以防止子组件意外地改变父组件的状态。

从Vue官方文档中,我们将找到两种方法来解决这个问题

  1. 如果子组件想使用道具作为本地数据,最好定义一个本地数据属性。

      props: ['list'],
    data: function() {
    return {
    localList: JSON.parse(this.list);
    }
    }
    
    
    
  2. The prop is passed in as a raw value that needs to be transformed. In this case, it’s best to define a computed property using the prop’s value:

      props: ['list'],
    computed: {
    localList: function() {
    return JSON.parse(this.list);
    },
    //eg: if you want to filter this list
    validList: function() {
    return this.list.filter(product => product.isValid === true)
    }
    //...whatever to transform the list
    }
    
    
    
    
    

答案很简单,您应该通过将值分配给一些局部组件变量(可以是数据属性,用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))都将被值监视器监视并分配给输入变量,一旦接收到输入,我们就可以捕捉该操作并向父元素发出输入,表明数据是从表单元素输入的。

< p >是的!, ve2中的属性突变是一种反模式。但是…
只要打破规则,用其他规则,继续前进! 你需要做的是在父作用域的组件属性中添加.sync修饰符
<your-awesome-components :custom-attribute-as-prob.sync="value" />

当TypeScript是你的首选lang。的发展

<template>
<span class="someClassName">
\{\{feesInLocale}}
</span>
</template>






@Prop({default: 0}) fees: any;


// computed are declared with get before a function
get feesInLocale() {
return this.fees;
}

而不是

<template>
<span class="someClassName">
\{\{feesInLocale}}
</span>
</template>






@Prop() fees: any = 0;
get feesInLocale() {
return this.fees;
}

下面是一个小吃店组件,当我像这样将间小吃店变量直接输入v-model时,if将工作,但在控制台中,它将给出一个错误

避免直接改变道具,因为每当父组件重新呈现时,该值将被覆盖。相反,应该使用基于道具值的数据或计算属性。

<template>
<v-snackbar v-model="snackbar">
\{\{ text }}
</v-snackbar>
</template>


<script>
export default {
name: "loader",
     

props: {
snackbar: {type: Boolean, required: true},
text: {type: String, required: false, default: ""},
},
      

}
</script>

消除这种突变错误的正确方法是使用观察家

<template>
<v-snackbar v-model="snackbarData">
\{\{ text }}
</v-snackbar>
</template>


<script>
/* eslint-disable */
export default {
name: "loader",
data: () => ({
snackbarData:false,
}),
props: {
snackbar: {type: Boolean, required: true},
text: {type: String, required: false, default: ""},
},
watch: {
snackbar: function(newVal, oldVal) {
this.snackbarData=!this.snackbarDatanewVal;
}
}
}
</script>

在加载小吃店的主组件中,你可以执行这段代码

<loader :snackbar="snackbarFlag" :text="snackText"></loader>

这对我很有用

Vue3有一个很好的解决方案。花了好几个小时才到那里。但是效果很好。

在父模板上

<user-name
v-model:first-name="firstName"
v-model:last-name="lastName"
></user-name>

子组件

app.component('user-name', {
props: {
firstName: String,
lastName: String
},
template: `
<input
type="text"
:value="firstName"
@input="$emit('update:firstName',
$event.target.value)">


<input
type="text"
:value="lastName"
@input="$emit('update:lastName',
$event.target.value)">
`
})

这是唯一能双向结合的解。我喜欢前两个答案,以良好的方式使用同步和发射更新事件,并计算属性getter setter,但这是一个工作要做的,我不喜欢这么努力工作。

一个潜在的解决方案是使用全局变量。

import { Vue } from "nuxt-property-decorator";


export const globalStore = new Vue({
data: {
list: [],
},
}


export function setupGlobalsStore() {
Vue.prototype.$globals = globalStore;
}

然后你可以用:

$globals.list

任何你需要变异或呈现它的地方。

将props分配给new变量。

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

您应该始终避免在vue或任何其他框架中改变道具。您可以采用的方法是将其复制到另一个变量中。

例如< p >。 // instead of replacing the value of this.list use a different variable < / p >

this.new_data_variable = JSON.parse(this.list)