我有一个带有嵌套对象的数据结构,我想将它们绑定到子组件,我希望这些组件直接编辑数据结构,以便可以将其全部保存在一个地方。结构类似于
job = {
id: 1,
uuid: 'a-unique-value',
content_blocks: [
{
id: 5,
uuid: 'some-unique-value',
block_type: 'text',
body: { en: { content: 'Hello' }, fr: { content: 'Bonjour' } }
},
{
id: 9,
uuid: 'some-other-unique-value',
block_type: 'text',
body: { en: { content: 'How are you?' }, fr: { content: 'Comment ça va?' } }
},
]
}
所以,我像这样实例化子组件
<div v-for="block in job.content_blocks" :key="block.uuid">
<component :data="block" :is="contentTypeToComponentName(block.block_type)" />
</div>
({contentTypeToComponentName
从text
到TextContentBlock
,这是组件的名称)
TextContentBlock
像这样
export default {
props: {
data: {
type: Object,
required: true
}
},
created: function() {
if (!this.data.body) {
this.data.body = {
it: { content: "" },
en: { content: "" }
}
}
}
}
created()
函数负责添加缺少的,特定于块的数据,这些数据对于添加新的content_blocks
的组件来说是未知的,例如,当我想通过特殊按钮动态添加块时,就像这个
addBlock: function(block_type) {
this.job.content_blocks = [...this.job.content_blocks, {
block_type: block_type,
uuid: magic_uuidv4_generator(),
order: this.job.content_blocks.length === 0 ? 1 : _.last(this.job.content_blocks).order + 1
}]
}
TextContentBlock
的模板是
<b-tab v-for="l in ['fr', 'en']" :key="`${data.uuid}-${l}`">
<template slot="title">
{{ l.toUpperCase() }} <span class="missing" v-show="!data.body[l] || data.body[l] == ''">(missing)</span>
</template>
<b-form-textarea v-model="data.body[l].content" rows="6" />
<div class="small mt-3">
<code>{{ { block_type: data.block_type, uuid: data.uuid, order: data.order } }}</code>
</div>
</b-tab>
现在,当我从API加载数据时,我可以正确地编辑并保存这些块的内容-考虑到道具应该是不可变的,这很奇怪。
但是,当我添加新块时,上面的textarea
不允许我编辑任何内容。我在其中输入内容,然后将其删除(或者,我认为它将其替换为“ previous”或“ initial”值)。从API提取内容时(例如,在页面加载时)不会发生这种情况。
无论如何,这导致了我发现不变性,然后我像这样创建了data
道具的本地副本
data: function() {
return {
block_data: this.data
}
}
,并将每个data
调整为block_data
,但我得到的行为与以前相同。
我到底想念什么?
答案 0 :(得分:0)
作为OP的注释,根本原因应该是如何在子组件和父组件之间同步textarea值。
OP遇到的问题应由父组件始终将相同的值传递给子组件内部的textarea,这导致即使在textarea中键入某些内容,它仍然绑定从父组件传递的相同值)
v-model本质上是语法糖,用于更新用户输入上的数据 事件,以及对某些极端情况的特殊照顾。
语法糖如下:
指令= v-model
将绑定值,然后监听input
事件以进行类似v-bind:value="val" v-on:input="val = $event.target.value"
因此,请调整您的代码,使其类似于下面的演示:
用于输入的textarea HTMLElement输入使用v-bind而不是v-model
然后使用$emit
将输入事件弹出到父组件
在父组件中,使用v-model
同步最新值。
Vue.config.productionTip = false
Vue.component('child', {
template: `<div class="child">
<label>{{value.name}}</label><button @click="changeLabel()">Label +1</button>
<textarea :value="value.body" @input="changeInput($event)"></textarea>
</div>`,
props: ['value'],
methods: {
changeInput: function (ev) {
let newData = Object.assign({}, this.value)
newData.body = ev.target.value
this.$emit('input', newData) //emit whole prop=value object, you can only emit value.body or else based on your design.
// you can comment out `newData.body = ev.target.value`, then you will see the result will be same as the issue you met.
},
changeLabel: function () {
let newData = Object.assign({}, this.value)
newData.name += ' 1'
this.$emit('input', newData)
}
}
});
var vm = new Vue({
el: '#app',
data: () => ({
options: [
{id: 0, name: 'Apple', body: 'Puss in Boots'},
{id: 1, name: 'Banana', body: ''}
]
}),
})
.child {
border: 1px solid green;
}
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<div id="app">
<span> Current: {{options}}</span>
<hr>
<div v-for="(item, index) in options" :key="index">
<child v-model="options[index]"></child>
</div>
</div>