我开始了 https://laracasts.com/series/learning-vue-step-by-step 系列。我在 Vue、Laravel 和 AJAX 课程上停了下来,但出现了以下错误:
vue.js:2574 [Vue 警告]:避免直接改变 prop,因为只要父组件重新渲染,该值就会被覆盖。相反,使用基于道具值的数据或计算属性。正在变异的道具:“列表”(在组件中找到)
我在 main.js 中有这段代码
Vue.component('task', {
template: '#task-template',
props: ['list'],
created() {
this.list = JSON.parse(this.list);
}
});
new Vue({
el: '.container'
})
当我覆盖列表道具时,我知道问题出在 created() 中,但我是 Vue 的新手,所以我完全不知道如何解决它。有谁知道如何(并请解释原因)修复它?
date() { return {val: null}}
并且在创建的部分中,您应该分配值 created() {this.val = JSON.parse(this.list)}
这与 mutating a prop locally is considered an anti-pattern in Vue 2
如果您想在本地修改 props,您现在应该做的是在您的 data
中声明一个使用 props
值作为其初始值的字段,然后修改副本:
Vue.component('task', {
template: '#task-template',
props: ['list'],
data: function () {
return {
mutableList: JSON.parse(this.list);
}
}
});
您可以在 Vue.js official guide 上阅读有关此内容的更多信息
注意 1: 请注意 you should not use the same name for your prop
and data
,即:
data: function () { return { list: JSON.parse(this.list) } } // WRONG!!
注意 2: 由于 I feel there is some confusion 关于 props
和 reactivity,我建议您查看 this 线程
Vue 模式是 props
向下和 events
向上。这听起来很简单,但在编写自定义组件时很容易忘记。
从 Vue 2.2.0 开始,您可以使用 v-model(和 computed properties)。我发现这种组合在组件之间创建了一个简单、干净且一致的接口:
传递给您的组件的任何道具都保持反应性(即,它不会被克隆,也不需要监视功能来在检测到更改时更新本地副本)。
更改会自动发送给父级。
可以与多个级别的组件一起使用。
计算属性允许单独定义 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 属性定义哪个 prop
与 v-model
相关联,以及在更改时将发出哪个事件。然后,您可以从父级调用此组件,如下所示:
<Task v-model="parentList"></Task>
listLocal
计算属性在组件内提供了一个简单的 getter 和 setter 接口(将其视为私有变量)。在 #task-template
中,您可以呈现 listLocal
,它会保持反应状态(即,如果 parentList
发生更改,它将更新 Task
组件)。您还可以通过调用 setter(例如 this.listLocal = newList
)来改变 listLocal
,它会将更改发送给父级。
这种模式的优点在于您可以将 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
调用其他子组件(例如表单元素)。
Vue 只是警告您:您更改了组件中的 prop,但是当父组件重新渲染时,“list”将被覆盖,您将丢失所有更改。所以这样做很危险。
像这样使用计算属性:
Vue.component('task', {
template: '#task-template',
props: ['list'],
computed: {
listJson: function(){
return JSON.parse(this.list);
}
}
});
如果你使用 Lodash,你可以在返回之前克隆 prop。如果您在父子节点上修改该道具,此模式会很有帮助。
假设我们在组件网格上有道具列表。
在父组件中
<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!)
您不应更改子组件中道具的值。如果您确实需要更改它,您可以使用 .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'
})
根据 VueJs 2.0,你不应该在组件内改变一个 prop。他们只是由他们的父母变异。因此,您应该在 data 中定义不同名称的变量,并通过查看实际 props 来保持更新。如果列表道具被父级更改,您可以对其进行解析并将其分配给 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);
}
}
});
它使用 mutableList 来呈现您的模板,因此您可以在组件中保持列表道具的安全。
答案很简单,您应该通过将值分配给一些局部组件变量(可以是数据属性,使用 getter、setter 或 watchers 计算)来打破直接的 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))都将被值观察者监视并分配给输入变量,一旦接收到输入,我们就可以捕获该动作并将输入发送给父级,表明数据已输入从表单元素。
data.name
或 smth clear,不依赖于视角,也不会将名称与事件冲突
v-model
的问题。显然,人们已经失去了理智,并积极提倡在任何组件上使用 v-model,无论该组件是否与用户输入有任何关系。 Vuetify 这样做会对他们的 v-dialog
和 v-bottom-sheet
组件产生巨大的负面影响。在对话框或底部工作表上监听 @input
近乎疯狂,因为这两个组件都不接受任何类型的输入 - 并且将它们编码为就好像它们接受一样是一种严重的反模式。
不要直接在组件中更改道具。如果需要更改它,请设置一个新属性,如下所示:
data () {
return () {
listClone: this.list
}
}
并更改 listClone 的值。
我也遇到过这个问题。在我使用 $on
和 $emit
后警告消失了。类似于建议使用 $on
和 $emit
将数据从子组件发送到父组件。
如果你想改变道具 - 使用 object.
<component :model="global.price"></component>
零件:
props: ['model'],
methods: {
changeValue: function() {
this.model.value = "new value";
}
}
您需要添加这样的计算方法
组件.vue
props: ['list'],
computed: {
listJson: function(){
return JSON.parse(this.list);
}
}
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'
})
也许这将满足您的需求。
我想给出这个答案,它有助于避免使用大量代码、观察者和计算属性。在某些情况下,这可能是一个很好的解决方案:
道具旨在提供单向通信。
当您有一个带有道具的模态 show/hide
按钮时,对我来说最好的解决方案是发出一个事件:
<button @click="$emit('close')">Close Modal</button>
然后将侦听器添加到模态元素:
<modal :show="show" @close="show = false"></modal>
(在这种情况下,可能不需要道具 show
,因为您可以直接在基本模式上使用简单的 v-if="show"
)
单向数据流,根据https://v2.vuejs.org/v2/guide/components.html,组件遵循单向数据流,所有的props在子属性和父属性之间形成单向向下绑定,当父属性更新时,它会向下流对子组件而不是相反,这可以防止子组件意外地改变父组件,这会使您的应用程序的数据流更难理解。
此外,每次父组件更新时,子组件中的所有道具都会刷新为最新值。这意味着您不应该尝试改变子组件内的道具。如果你这样做,.vue 会在控制台中警告你。
通常有两种情况很容易改变一个 prop: prop 用于传入初始值;子组件之后希望将其用作本地数据属性。 prop 作为需要转换的原始值传入。这些用例的正确答案是: 定义一个使用 prop 的初始值作为其初始值的本地数据属性:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
定义一个根据 prop 的值计算的计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
Vue.js 的 props 不能被改变,因为这在 Vue 中被认为是一种反模式。
您需要采取的方法是在组件上创建一个引用 list 的原始 prop 属性的数据属性
props: ['list'],
data: () {
return {
parsedList: JSON.parse(this.list)
}
}
现在,传递给组件的列表结构通过组件的 data
属性被引用和改变:-)
如果您希望做的不仅仅是解析列表属性,请使用 Vue 组件的 computed
属性。这使您可以对道具进行更深入的突变。
props: ['list'],
computed: {
filteredJSONList: () => {
let parsedList = JSON.parse(this.list)
let filteredList = parsedList.filter(listItem => listItem.active)
console.log(filteredList)
return filteredList
}
}
上面的示例解析您的列表道具并将其过滤为仅活动列表项,将其注销以查看 schnitts 和 gggles 并返回它。
注意:data
和computed
属性在模板中引用相同,例如
<pre>{{parsedList}}</pre>
<pre>{{filteredJSONList}}</pre>
很容易认为需要调用 computed
属性(作为方法)......它不需要
当 TypeScript 是你的首选语言时。发展的
<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;
}
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)">
`
})
这是唯一进行双向绑定的解决方案。我喜欢前两个答案是使用 SYNC 和 Emitting 更新事件以及计算属性 getter 设置器的好方法,但那是一项工作,我不喜欢这么努力。
将道具分配给新变量。
data () {
return {
listClone: this.list
}
}
添加到最佳答案,
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.parse 错误。
是的!,vue2 中的变异属性是反模式。但是……只要用其他规则来打破规则,然后继续前进!您需要将 .sync 修饰符添加到 paret 范围内的组件属性。 <your-awesome-components :custom-attribute-as-prob.sync="value" />
🤡我们杀了蝙蝠侠很简单😁
下面是一个小吃店组件,当我像这样将小吃店变量直接输入 v-model 时,如果可以工作,但在控制台中,它会给出一个错误
避免直接改变 prop,因为只要父组件重新渲染,该值就会被覆盖。相反,使用基于道具值的数据或计算属性。
<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>
这对我有用
Vue.js 认为这是一种反模式。例如,声明和设置一些道具,如
this.propsVal = 'new Props Value'
因此,要解决这个问题,您必须从 Vue 实例的数据或计算属性的 props 中获取一个值,如下所示:
props: ['propsVal'],
data: function() {
return {
propVal: this.propsVal
};
},
methods: {
...
}
这肯定会奏效。
除上述内容外,对于其他有以下问题的人:
“如果不需要 props 值因此并不总是返回,则传递的数据将返回 undefined
(而不是空)”。这可能会弄乱 <select>
默认值,我通过检查该值是否在 beforeMount()
中设置(如果没有设置)来解决它,如下所示:
JS:
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:
<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 props 是一种数据流动的方式,这可以防止子组件意外改变父组件的状态。
从 Vue 官方文档中,我们会找到 2 种方法来解决这个问题
如果子组件想要使用 props 作为本地数据,最好定义一个本地数据属性。道具:['list'],数据:function() { return { localList: JSON.parse(this.list); prop 作为需要转换的原始值传入。在这种情况下,最好使用 prop 的值定义计算属性: 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) } //...不管怎样转换列表 }
您应该始终避免在 vue 或任何其他框架中改变 props。您可以采取的方法是将其复制到另一个变量中。
例如。 // instead of replacing the value of this.list use a different variable
this.new_data_variable = JSON.parse(this.list)
一个潜在的解决方案是使用全局变量。
import { Vue } from "nuxt-property-decorator";
export const globalStore = new Vue({
data: {
list: [],
},
}
export function setupGlobalsStore() {
Vue.prototype.$globals = globalStore;
}
然后你会使用:
$globals.list
任何你需要对其进行变异或呈现的地方。
不定期副业成功案例分享