Поле ввода не может изменить модель при динамическом создании компонента и модели - PullRequest
0 голосов
/ 27 июня 2018

У меня есть структура данных с вложенными объектами, которую я хочу связать с подкомпонентами, и я хотел бы, чтобы эти компоненты редактировали структуру данных напрямую, чтобы я мог сохранить все это из одного места. Структура что-то вроде

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 выше не позволит мне что-либо редактировать. Я ввожу в него материал, и он просто удаляет его (или, я думаю, он заменяет его «предыдущим» или «начальным» значением). Этого не происходит при извлечении контента из API (скажем, при загрузке страницы).

Во всяком случае, это привело меня к открытию неизменности, затем я создал локальную копию data реквизита, как это

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

и каждый data корректируется до block_data, но я получаю то же поведение, что и раньше.

Что именно мне не хватает?

1 Ответ

0 голосов
/ 27 июня 2018

Как отмечает OP, основная причина должна заключаться в том, как синхронизировать значение textarea между дочерним и родительским компонентом.

Проблема, с которой столкнулся OP, должна быть вызвана тем, что родительский компонент всегда передает одно и то же значение текстовой области внутри дочернего компонента, что приводит к четному вводу чего-либо в текстовой области, он по-прежнему связывает то же значение, которое было передано из родительского компонента. )

Как Vue Guide сказал :

v-модель по сути является синтаксическим сахаром для обновления данных по пользовательскому вводу мероприятия, а также особое внимание к некоторым крайним случаям.

Синтаксический сахар будет выглядеть так:

директива = v-model свяжет значение, затем прослушает событие input для внесения изменений, например v-bind:value="val" v-on:input="val = $event.target.value"

Поэтому настройте свои коды так, как показано ниже:

  1. для ввода, textarea HTMLElement, использует v-bind вместо v-модели

  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>
...