提问人:James Lin 提问时间:9/17/2021 最后编辑:benson23James Lin 更新时间:7/16/2022 访问量:3584
Vue 组件中的道具是可变的吗?
Are the props in a Vue component mutable?
问:
人!最近,我正在做一个Vue.js项目,我想尽可能地将其拆分为小组件。有一个组件从父组件渲染列表,我想从道具中更改某些动作的一些属性。在这一点上,我只是好奇 props 变量在 Vue.js 组件中是否可变。
下面是一个示例代码,以便更好地理解:
<template>
<div>
<div
class="d-flex align-items-center mb-2 justify-between"
v-for="(option, index) in options"
:key="index"
>
<b-input-group>
<b-input-group-prepend>
<b-button variant="secondary">
<b-icon icon="grip-horizontal"></b-icon>
</b-button>
</b-input-group-prepend>
<b-form-input
placeholder="Option"
@input="updateOption(index, $event)"
:value="option.answer"
></b-form-input>
<b-input-group-append>
<b-button variant="secondary" @click="removeOption(index)">
Remove
</b-button>
</b-input-group-append>
</b-input-group>
<b-form-checkbox
:id="`option-${index}`"
name="options"
class="f-14 text-muted ml-1"
v-model="option.correct"
>
Correct?
</b-form-checkbox>
</div>
</div>
</template>
<script>
export default {
name: 'multi-options',
props: ['options'],
methods: {
updateOption(index, value) {
this.options[index].answer = value // Should this work or not?
},
removeOption(index) {
this.options.splice(index, 1)
},
addOption() {
this.options.push({
answer: '',
correct: false,
})
},
},
}
</script>
我感谢任何评论。
答:
你不应该从组件本身改变道具。从文档来看,这是为了防止子组件意外更改父组件状态,这可能会使应用的数据流更难理解。
您可以做的是从子组件发出自定义事件:
updateOption(index, value) {
this.$emit("myUpdateEvent", { index, value });
}
removeOption(index) {
this.$emit("myRemoveEvent", { index });
}
addOption() {
this.$emit("myAddEvent");
}
然后在父组件中侦听它们,并对作为道具传递给子组件的原始数组进行更改。
<multi-options @myAddEvent="handleAddEvent()" @myUpdateEvent="handleUpdateEvent($event.index, $event.value)" @myRemoveEvent="handleRemoveEvent($event.index)" :options="myOptions"/>
data() {
return {
myOptions: [],
}
},
methods: {
handleUpdateEvent(index, value) {
this.myOptions[index].answer = value
},
handleRemoveEvent(index) {
this.myOptions.splice(index, 1);
},
handleAddEvent() {
this.myOptions.push({
answer: '',
correct: false,
});
}
}
评论
不,绝不。父子之间的 Vue js 通信是单向的。从父母那里收到的道具不应该被变异,否则,如果你在启用严格模式的情况下工作,你会收到警告。
从父母到孩子的数据必须以 .从子项到父级的数据必须在props
events
这里最好的方法是在子组件中本地操作数据,方法是在挂载组件时设置本地数据,然后在发生更改时向父组件发出事件(仅当需要让父组件知道发生了某些事情时)
props: ['options'],
data: () => ({
local_options: []
}),
mounted () {
this.local_options = [ ...this.options]
},
watch: {
options: function () {
deep: true,
handler () {
this.local_options = [ ...this.options]
}
}
},
methods: {
updateOption(index, value) {
this.local_options[index].answer = value
this.$emit('updated', this.local_options)
},
removeOption(index) {
this.local_options.splice(index, 1)
this.$emit('updated', this.local_options)
},
addOption() {
this.local_options.push({
answer: '',
correct: false,
})
this.$emit('updated', this.local_options)
},
},
然后,在父级中,在父级中,您可以根据子级事件执行某些操作:
<multi-options-component :options="options" @updated="doSomething" />
....
methods: {
doSomething (options) {
// Some logic here
}
}
评论
local_options
mounted
doSomething
local_options
options
mounted
我相信 @Luciano 更喜欢为孩子设置一个单独的本地状态并手动维护其反应性的方式是灾难的根源(更不用说考虑到 Vue 已经内置了反应性,它的方式太过分了)。我在评论中提到了这一点,我将在这里再次添加:
- 如果由于任何原因失败,则父数组的数组将不会更新,而数组已在组件中更新。现在,父州和本地州不同步,您有问题。
doSomething
options
local_options
- 无需深入使用您构建的每个可重用组件。如果有数百个这样的子组件,则性能会受到影响,并且不必要地消耗大量内存来为每个子组件创建和维护单独的本地状态。
watcher
- 你正在失去 Vue 在尝试手动确保(尽管是以一种容易出错的方式)本地状态始终与父级同步时提供的反应优势。这不是你应该如何使用Vue.js。
因此,我添加了另一个答案,该答案在可重用性和 DRY 原则方面比我之前的答案更好,同时也简单且无混乱:
您需要将数组与单个自定义事件一起传递回父级,但不是在生命周期挂钩中创建此数组,而是在每个方法中随时随地创建它:update
mounted
props: ['options'],
methods: {
updateOption(index, value) {
let local_options = [ ...this.options]
local_options[index].answer = value
this.$emit('update', local_options)
},
removeOption(index) {
let local_options = [ ...this.options]
local_options.splice(index, 1)
this.$emit('update', local_options)
},
addOption() {
let local_options = [ ...this.options]
local_options.push({
answer: '',
correct: false,
})
this.$emit('update', local_options)
},
},
然后,当子组件发出带有指令的事件或其简写时,更新父组件的数据option
update
v-on:
@
<multi-options :options="options" @update="handleUpdate" />
....
methods: {
handleUpdate(options) {
this.options = options; // this reassignment re-renders the child component
}
}
编辑:我错过了你在元素中使用的道具。这将创建与 prop 的双向绑定,因为它转换为 .这意味着您正在更改子组件中的道具。请改为执行以下操作:v-model
<b-form-checkbox>
v-model="option.correct"
:checked="option.correct" @change="option.correct = $event"
<b-form-checkbox
:id="`option-${index}`"
name="options"
class="f-14 text-muted ml-1"
:checked="option.correct"
@change="setOptionsCorrect(index, $event)"
>
Correct?
</b-form-checkbox>
将方法添加到您的:methods
setOptionsCorrect(index, value) {
let local_options = [ ...this.options]
local_options[index].correct = value
this.$emit('update', local_options)
}
编辑 2:如果您坚持使用样式首选项,则可以从选项派生计算属性。但是你需要一个单独的 setter 将事件发出给父级,否则你会遇到同样的“变异道具”问题v-model
computed : {
computedOptions: {
get: function() {
return this.options
},
set: function(newOptions) {
this.$emit("update", newOptions)
}
}
},
methods: {
updateOption(index, value) {
let local_options = [ ...this.options]
local_options[index].answer = value
this.options = local_options
},
removeOption(index) {
let local_options = [ ...this.options]
local_options.splice(index, 1)
this.options = local_options
},
addOption() {
let local_options = [ ...this.options]
local_options.push({
answer: '',
correct: false,
})
this.options = local_options
},
}
评论
computed
评论