如何防止 Child 组件中更新的数据更新 VueJS Parent 中的数据 [2.6.14]
How to prevent updated data in Child component from updating Parent's data in VueJS [2.6.14]
我正在使用 vuejs 2.6.14 并且 运行 遇到以下问题:
来自 child 组件的修改数据也会更新 parent 组件中的数据,而无需在代码中使用任何 $emit。
这与通常的“如何从parent更新child中的数据/如何从child更新parent中的数据”相反
这是我的代码的更详细信息:
我有一个名为 Testing.vue 的 parent 组件,将 JSON object(“userData”)传递给 child,
GeneralData.vue.
这是 parent 的代码:
<template>
<div id="testing-compo">
<div style="margin-top: 1rem; margin-bottom: 1rem; max-width: 15rem">
<label
class="sr-only"
for="inline-form-input-username"
style="margin-top: 1rem; margin-bottom: 1rem"
>Account settings for :</label
>
<b-form-input
v-model="username"
id="inline-form-input-username"
placeholder="Username"
:state="usernameIsValid"
></b-form-input>
</div>
<b-button class="button" variant="outline-primary"
@click="callFakeUser">
Populate fake user
</b-button>
<GeneralData :userData="user" />
</div>
</template>
<script>
export default {
name: "Testing",
components: {
GeneralData,
},
data() {
return {
user: null,
username: null,
};
},
computed: {
usernameIsValid: function () {
if (this.username != null && this.username.length >= 4) {
return true;
} else if (this.username != null) {
return false;
}
return null;
},
},
methods: {
async callFakeUser() {
userServices.getFakeUser().then((res) => {
this.user = res;
console.log(this.user);
});
},
</script>
一个非常简单的测试组件,调用 userServices.getFakeUser(),异步 returns a JSON object.
对于child:
<template>
<div id="general-compo">
<!-- AGE -->
<div class="mt-2">
<label for="text-age">Age</label>
<div>
<b-form-input
v-model="userAge"
placeholder="+18 only"
class="w-25 p-1"
type="number"
>
</b-form-input>
</div>
</div>
<!-- LANGUAGES -->
<div class="mt-2">
<label for="lang-list-id">Language(s)</label>
<div
v-for="langKey in userLangsCount"
:key="langKey"
style="display: flex; flex-direction: row"
>
<b-form-input
readonly
:placeholder="userLangs[langKey - 1]"
style="max-width: 50%; margin-top: 0.5rem"
disabled
></b-form-input>
**This form is set to read only, for display purposes only**
<b-button
variant="outline-danger"
@click="removeLang(langKey)"
style="margin-top: 0.5rem; margin-left: 1rem"
>Remove</b-button
>
**This button removes a language from the userLangs array by calling removeLang(langKey)**
</div>
<div style="display: flex; flex-direction: row">
<b-form-input
v-model="userCurrentLang"
list="langlist-id"
placeholder="Add Language"
style="max-width: 50%; margin-top: 0.5rem"
></b-form-input>
<datalist id="langlist-id">
<option>Manual Option</option>
<option v-for="lang in langList" :key="lang.name">
{{ lang.name }}
</option>
</datalist>
<b-button
:disabled="addLangBtnDisabled"
variant="outline-primary"
@click="addLang()"
style="margin-top: 0.5rem; margin-left: 1rem"
>Add</b-button
>
</div>
</div>
</div>
</template>
<script>
import langList from "../assets/langList";
export default {
name: "GeneralData",
components: {},
props: {
userData: Object,
},
data() {
return {
userAge: null,
langList: langList,
userLangs: [],
userCurrentLang: null,
};
},
watch: {
//Updating tabs with fetched values
userData: function () {
this.userLangs = this.userData.general.langs;
this.userAge = this.userData.general.age
},
},
computed: {
**userGeneral is supposed to represent the data equivalent of userData.general, it is therefore computed from the user input, its value is updated each time this.userAge or this.userLangs changes**
userGeneral: function () {
//user data in data() have been filled with userData values
return {
age: this.userAge,
langs: this.userLangs,
};
},
**returns the amount of languages spoken by the user to display them in a v-for loop**
userLangsCount: function () {
if (this.userLangs) {
return this.userLangs.length;
}
return 0;
},
**gets a list of languages name from the original JSON list for display purposes**
langNameList: function () {
let namelist = [];
for (let i = 0; i < this.langList.length; i++) {
namelist.push(langList[i].name);
}
return namelist;
},
**returns true or false depending on whether entered language is in original list**
addLangBtnDisabled: function () {
for (let i = 0; i < this.langList.length; i++) {
if (this.userCurrentLang == langList[i].name) {
return false;
}
}
return true;
},
},
methods: {
addLang() {
this.userLangs.push(this.userCurrentLang);
this.userCurrentLang = null;
},
removeLang(key) {
this.userLangs.splice(key - 1, 1);
},
}
}
</script>
这是在 Testing.vue 中更新 this.user 后浏览器内 vuejs 开发工具中的数据:
Testing.vue中的数据:
user : {
general:{"age":22,"langs":["French"]}
}
GeneralData.vue中的数据:
userData : {
general:{"age":22,"langs":["French"]}
}
userAge : 22
userLangs : ["French"]
userGeneral :
{
general:{"age":22,"langs":["French"]}
}
到目前为止一切顺利吧?
这就是问题所在,如果我更改表单中的年龄字段,userAge 会增加,userGeneral.age 会更新,但 userData.general.age 不会。这是预期的,因为 userGeneral.age 是根据 this.userAge 计算出来的,而 userData 是一个道具,因此不应将其作为一种好的做法进行突变(无论如何都不是方法集 userData.general.age = xxx) .
但是,如果我点击语言列表中法语旁边的“删除”按钮,this.userLangs 会按原样更新,现在是 [],this.userGeneral.langs 也会更新为 []直接从前者计算。 userData.general.langs ... 也更新为 [],这对我来说真的毫无意义。
更糟糕的是,在parent、Testing.vue、user.general.langs现在也设置为[]。
不知何故,this.userLangs 更新了道具 this.userData,并且这个道具更新了它在 parent 组件中的原始发件人用户,尽管没有 $涉及任何类型的发射。
我不希望这种情况发生,因为 我不认为它应该以这种方式发生,因此是一个 hasard,而且因为我想设置一个 'save' 按钮稍后允许用户一次修改他的值。
我尝试过的方法:在删除/添加按钮上的@click 元素上设置各种 .prevent、.stop,在调用自身的方法中,添加 e.preventDefault(将 addLang 和 removeLang 修改为也发送 $event 元素),none 这些尝试都解决了任何问题。
希望我没有正确实现 .prevent 部分,有人可以帮我解决这个 reverse-flow 烦人的问题。
这里问题的解决方案是 lang 是一个作为引用传递的数组,因此突变会冒泡到父级。
除了分配原始数组,我们还可以分配一个
的副本
userData: function () { this.userLangs = [...this.userData.general.langs]; this.userAge = this.userData.general.age }
我正在使用 vuejs 2.6.14 并且 运行 遇到以下问题:
来自 child 组件的修改数据也会更新 parent 组件中的数据,而无需在代码中使用任何 $emit。
这与通常的“如何从parent更新child中的数据/如何从child更新parent中的数据”相反
这是我的代码的更详细信息:
我有一个名为 Testing.vue 的 parent 组件,将 JSON object(“userData”)传递给 child, GeneralData.vue.
这是 parent 的代码:
<template>
<div id="testing-compo">
<div style="margin-top: 1rem; margin-bottom: 1rem; max-width: 15rem">
<label
class="sr-only"
for="inline-form-input-username"
style="margin-top: 1rem; margin-bottom: 1rem"
>Account settings for :</label
>
<b-form-input
v-model="username"
id="inline-form-input-username"
placeholder="Username"
:state="usernameIsValid"
></b-form-input>
</div>
<b-button class="button" variant="outline-primary"
@click="callFakeUser">
Populate fake user
</b-button>
<GeneralData :userData="user" />
</div>
</template>
<script>
export default {
name: "Testing",
components: {
GeneralData,
},
data() {
return {
user: null,
username: null,
};
},
computed: {
usernameIsValid: function () {
if (this.username != null && this.username.length >= 4) {
return true;
} else if (this.username != null) {
return false;
}
return null;
},
},
methods: {
async callFakeUser() {
userServices.getFakeUser().then((res) => {
this.user = res;
console.log(this.user);
});
},
</script>
一个非常简单的测试组件,调用 userServices.getFakeUser(),异步 returns a JSON object.
对于child:
<template>
<div id="general-compo">
<!-- AGE -->
<div class="mt-2">
<label for="text-age">Age</label>
<div>
<b-form-input
v-model="userAge"
placeholder="+18 only"
class="w-25 p-1"
type="number"
>
</b-form-input>
</div>
</div>
<!-- LANGUAGES -->
<div class="mt-2">
<label for="lang-list-id">Language(s)</label>
<div
v-for="langKey in userLangsCount"
:key="langKey"
style="display: flex; flex-direction: row"
>
<b-form-input
readonly
:placeholder="userLangs[langKey - 1]"
style="max-width: 50%; margin-top: 0.5rem"
disabled
></b-form-input>
**This form is set to read only, for display purposes only**
<b-button
variant="outline-danger"
@click="removeLang(langKey)"
style="margin-top: 0.5rem; margin-left: 1rem"
>Remove</b-button
>
**This button removes a language from the userLangs array by calling removeLang(langKey)**
</div>
<div style="display: flex; flex-direction: row">
<b-form-input
v-model="userCurrentLang"
list="langlist-id"
placeholder="Add Language"
style="max-width: 50%; margin-top: 0.5rem"
></b-form-input>
<datalist id="langlist-id">
<option>Manual Option</option>
<option v-for="lang in langList" :key="lang.name">
{{ lang.name }}
</option>
</datalist>
<b-button
:disabled="addLangBtnDisabled"
variant="outline-primary"
@click="addLang()"
style="margin-top: 0.5rem; margin-left: 1rem"
>Add</b-button
>
</div>
</div>
</div>
</template>
<script>
import langList from "../assets/langList";
export default {
name: "GeneralData",
components: {},
props: {
userData: Object,
},
data() {
return {
userAge: null,
langList: langList,
userLangs: [],
userCurrentLang: null,
};
},
watch: {
//Updating tabs with fetched values
userData: function () {
this.userLangs = this.userData.general.langs;
this.userAge = this.userData.general.age
},
},
computed: {
**userGeneral is supposed to represent the data equivalent of userData.general, it is therefore computed from the user input, its value is updated each time this.userAge or this.userLangs changes**
userGeneral: function () {
//user data in data() have been filled with userData values
return {
age: this.userAge,
langs: this.userLangs,
};
},
**returns the amount of languages spoken by the user to display them in a v-for loop**
userLangsCount: function () {
if (this.userLangs) {
return this.userLangs.length;
}
return 0;
},
**gets a list of languages name from the original JSON list for display purposes**
langNameList: function () {
let namelist = [];
for (let i = 0; i < this.langList.length; i++) {
namelist.push(langList[i].name);
}
return namelist;
},
**returns true or false depending on whether entered language is in original list**
addLangBtnDisabled: function () {
for (let i = 0; i < this.langList.length; i++) {
if (this.userCurrentLang == langList[i].name) {
return false;
}
}
return true;
},
},
methods: {
addLang() {
this.userLangs.push(this.userCurrentLang);
this.userCurrentLang = null;
},
removeLang(key) {
this.userLangs.splice(key - 1, 1);
},
}
}
</script>
这是在 Testing.vue 中更新 this.user 后浏览器内 vuejs 开发工具中的数据:
Testing.vue中的数据:
user : {
general:{"age":22,"langs":["French"]}
}
GeneralData.vue中的数据:
userData : {
general:{"age":22,"langs":["French"]}
}
userAge : 22
userLangs : ["French"]
userGeneral :
{
general:{"age":22,"langs":["French"]}
}
到目前为止一切顺利吧?
这就是问题所在,如果我更改表单中的年龄字段,userAge 会增加,userGeneral.age 会更新,但 userData.general.age 不会。这是预期的,因为 userGeneral.age 是根据 this.userAge 计算出来的,而 userData 是一个道具,因此不应将其作为一种好的做法进行突变(无论如何都不是方法集 userData.general.age = xxx) . 但是,如果我点击语言列表中法语旁边的“删除”按钮,this.userLangs 会按原样更新,现在是 [],this.userGeneral.langs 也会更新为 []直接从前者计算。 userData.general.langs ... 也更新为 [],这对我来说真的毫无意义。
更糟糕的是,在parent、Testing.vue、user.general.langs现在也设置为[]。
不知何故,this.userLangs 更新了道具 this.userData,并且这个道具更新了它在 parent 组件中的原始发件人用户,尽管没有 $涉及任何类型的发射。
我不希望这种情况发生,因为 我不认为它应该以这种方式发生,因此是一个 hasard,而且因为我想设置一个 'save' 按钮稍后允许用户一次修改他的值。
我尝试过的方法:在删除/添加按钮上的@click 元素上设置各种 .prevent、.stop,在调用自身的方法中,添加 e.preventDefault(将 addLang 和 removeLang 修改为也发送 $event 元素),none 这些尝试都解决了任何问题。
希望我没有正确实现 .prevent 部分,有人可以帮我解决这个 reverse-flow 烦人的问题。
这里问题的解决方案是 lang 是一个作为引用传递的数组,因此突变会冒泡到父级。 除了分配原始数组,我们还可以分配一个
的副本userData: function () { this.userLangs = [...this.userData.general.langs]; this.userAge = this.userData.general.age }