【问题标题】:Input field unable to change model when component and model are dynamically created动态创建组件和模型时输入字段无法更改模型
【发布时间】:2018-12-06 13:26:38
【问题描述】:

我有一个包含嵌套对象的数据结构,我想将其绑定到子组件,并且我希望这些组件直接编辑数据结构,以便我可以从一个地方将其全部保存。结构有点像

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>

contentTypeToComponentNametext 变为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 加载数据时,我可以正确编辑和保存这些块的内容——考虑到 props 应该是不可变的,这很奇怪。

但是,当我添加新块时,上面的 textarea 不允许我编辑任何内容。我在其中输入内容,它只是将其删除(或者,我认为,它用“先前”或“初始”值替换它)。从 API 提取内容时(例如,在页面加载时)不会发生这种情况。

无论如何,这让我发现了不可变性,然后我像这样创建了 data 道具的本地副本

data: function() {
    return {
        block_data: this.data
    }
}

并将每个data 调整为block_data,但我得到与以前相同的行为。

我到底错过了什么?

【问题讨论】:

  • 能否提供stackoverflow.com/help/mcve。该示例在当前形式下不是很有用。
  • 你检查过 Vue 开发工具发生了什么?
  • 您提供的乱码。但您的代码似乎修改了props=data 的值。试试data: function() { return { block_data: Object.assign({}, this.data) } }addBlock 的输出是什么?似乎没有创建 property=body。
  • Andrey:我做到了,整个数据结构得到了正确构造的对象。
  • @Morpheu5 检查了答案中的演示,如果解决了您的问题,请告诉我。

标签: javascript vue.js vue-component


【解决方案1】:

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

OP遇到的问题应该是父组件总是向子组件内的textarea传递相同的值,这导致即使在textarea中输入一些东西,它仍然绑定从父组件传递的相同值)

作为 Vue Guide said:

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

语法糖如下:

directive=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>

【讨论】:

  • 嘿,您的回答为我指明了正确的方向,所以我接受了它,但不是在我在某处发布我的解决方案之前,所以我可以更新我的问题以参考我实际上是如何解决这个问题的——这与您的方法没有太大不同,只是我有一些额外的问题需要解决。
猜你喜欢
  • 2019-10-04
  • 1970-01-01
  • 1970-01-01
  • 2013-09-06
  • 2011-04-12
  • 2018-09-12
  • 2017-12-28
  • 2017-04-26
  • 1970-01-01
相关资源
最近更新 更多