动态创建组件和模型时,输入字段无法更改模型

时间:2018-06-27 15:18:21

标签: javascript vue.js vue-component

我有一个带有嵌套对象的数据结构,我想将它们绑定到子组件,我希望这些组件直接编辑数据结构,以便可以将其全部保存在一个地方。结构类似于

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>

({contentTypeToComponentNametextTextContentBlock,这是组件的名称)

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,但我得到的行为与以前相同。

我到底想念什么?

1 个答案:

答案 0 :(得分:0)

作为OP的注释,根本原因应该是如何在子组件和父组件之间同步textarea值。

OP遇到的问题应由父组件始终将相同的值传递给子组件内部的textarea,这导致即使在textarea中键入某些内容,它仍然绑定从父组件传递的相同值)

Vue Guide said

  

v-model本质上是语法糖,用于更新用户输入上的数据   事件,以及对某些极端情况的特殊照顾。

语法糖如下:

指令= v-model将绑定值,然后监听input事件以进行类似v-bind:value="val" v-on:input="val = $event.target.value"

的更改

因此,请调整您的代码,使其类似于下面的演示:

  1. 用于输入的textarea HTMLElement输入使用v-bind而不是v-model

  2. 然后使用$emit将输入事件弹出到父组件

  3. 在父组件中,使用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>