API композиции Vue не реагирует внутри v-for?

Я использую Vue 2 и Vuetify (не Vue 3) для создания веб-сайта конструктора форм. У меня все шло отлично, пока я не обнаружил, что что-то не так. Так вот дело. Я визуализировал текстовые поля (входы) из реактивного массива, используя следующий код.

<template>
// ... some other unrelated code
<template v-if="answer.type !== 1">
  <v-col 
    v-for="(_, i) in answer.options" 
    :key="`#question-${answer.id}-${i}`" 
    cols="12"
  >
    <div class="flex flex-row justify-between items-center">
      <TextField
        v-model="answer.options[i]"
        class="ml-4" 
        label="Option"
        underlined 
        hideLabel 
      />
      <v-btn @click="deleteOption(i)" icon>
        <v-icon>mdi-close</v-icon>
      </v-btn>
    </div>
  </v-col>
  <v-col>
    <TextButton @click="addOption()" text="+ ADD OPTION" />
  </v-col>
</template>
// ... some other unrelated code
</template>

<script>
  import { reactive, ref, watch } from '@vue/composition-api'
  import useInquiry from '@/composables/useInquiry'
  import TextButton from '@/components/clickables/TextButton.vue'
  import TextField from '@/components/inputs/TextField.vue'

  export default {
    components: { TextField, TextButton },
    setup() {
      const { answer, addOption, deleteOption } = useInquiry()

      return { answer, addOption, deleteOption }
    }
  }
</script>

Вот мое использование составной логики запроса

import { reactive, watch, se } from '@vue/composition-api'
import ID from '@/helpers/id'

export default () => {
  const answer = reactive({
    id: ID(), // this literally just generates an ID
    type: 2,
    options: ['', '']
  })

  const addOption = () => {
    answer.options.push('')
  }

  const deleteOption = at => {
    const temp = answer.options.filter((_, i) => i !== at)
    answer.options = []
    answer.options = temp
  };

  return { answer, addOption, deleteOption }
}

И, наконец, вот мой TextField.vue

<template>
  <v-text-field 
    v-model="inputValue"
    :label="label"
    :single-line="hideLabel"
    :type="password ? 'password' : 'text'"
    :outlined="!underlined"
    :dense="!large"
    hide-details="auto"
  />
</template>

<script>
  import { ref, watch } from '@vue/composition-api'

  export default {
    model: {
      props: 'value',
      event: 'change'
    },
    props: {
      label: String,
      value: String,
      password: Boolean,
      underlined: Boolean,
      large: Boolean,
      hideLabel: Boolean
    },
    setup(props, context) {
      const inputValue = ref(props.value)

      watch(inputValue, (currInput, prevInput) => {
        context.emit('change', currInput)
      })

      return { inputValue }
    }
  }
</script>

Проблема в том, что каждый раз, когда нажимается кнопка удаления, удаленный ввод всегда является последним в массиве, даже если я не нажимал на последний. Я попытался зарегистрировать свой реактивный массив, наблюдая за ним с помощью метода отслеживания композиции Vue. Судя по всему, данные корректно обновляются. Проблема в том, что v-модель выглядит несинхронизированной, и последний вход всегда тот, который удаляется.


person Leng Long    schedule 22.10.2020    source источник
comment
Думаю, я заметил настоящую проблему ... проверьте мой обновленный ответ ...   -  person Michal Levý    schedule 22.10.2020


Ответы (1)


Мне нравится .... кроме TextField.vue

Проблема в TextField.vue. Что вы на самом деле делаете внутри setup из TextField.vue, так это (Vue 2 API):

data() {
  return {
    inputValue: this.value
  }
}

... это однократная инициализация inputValue элемента данных со значением value prop. Поэтому, когда один из options удаляется и компоненты используются повторно (потому что это то, что Vue делает все время - особенно когда индекс используется в :key) inputValue не обновляется до нового значения value prop ...

Вам вообще не нужна опция inputValue или model, просто удалите ее и используйте этот шаблон:

<v-text-field 
    :value="value"
    @input="$emit('input', $event)"
    :label="label"
    :single-line="hideLabel"
    :type="password ? 'password' : 'text'"
    :outlined="!underlined"
    :dense="!large"
    hide-details="auto"
  />

ОБРАТИТЕ ВНИМАНИЕ, что в моем примере я использую $event.target.value вместо просто $event, потому что я работаю с собственным элементом <input>, а не с вводом пользовательского компонента Vuetify ...

working example...

const {
  reactive,
  ref,
  watch
} = VueCompositionAPI
Vue.use(VueCompositionAPI)

const BrokenInput = {
  model: {
    props: 'value',
    event: 'change'
  },
  props: {
    value: String,
  },
  setup(props, context) {
    const inputValue = ref(props.value)

    watch(inputValue, (currInput, prevInput) => {
      context.emit('change', currInput)
    })

    return {
      inputValue
    }
  },
  template: `<input type="text" v-model="inputValue" />`
}

const FixedInput = {
  props: {
    value: String,
  },
  template: `<input type="text" :value="value" @input="$emit('input', $event.target.value)"/>`
}

const useInquiry = () => {
  const answer = reactive({
    id: 1,
    type: 2,
    options: ['1', '2', '3', '4', '5']
  })

  const addOption = () => {
    answer.options.push('')
  }

  const deleteOption = at => {
    const temp = answer.options.filter((_, i) => i !== at)
    answer.options = temp
  };

  return {
    answer,
    addOption,
    deleteOption
  }
}

const app = new Vue({
  components: { 'broken-input': BrokenInput, 'fixed-input': FixedInput },
  setup() {
    return useInquiry()
  },
})
app.$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@vue/[email protected]"></script>
<div id="app">
  <table>
    <tr>
      <th>Option</th>
      <th>Broken input</th>
      <th>Model</th>
      <th>Fixed input</th>
      <th></th>
      <tr>
        <tr v-for="(_, i) in answer.options" :key="'#question-'+ answer.id + '-' + i">
          <td>Option {{ i }}:</td>
          <td>
            <broken-input v-model="answer.options[i]" />
          </td>
          <td>
            {{ answer.options[i] }}
          </td>
          <td>
            <fixed-input v-model="answer.options[i]" />
          </td>
          <td><button @click="deleteOption(i)">Remove option</button></td>
        </tr>
  </table>
  <button @click="addOption()">Add option</button>
</div>

person Michal Levý    schedule 22.10.2020
comment
Спасибо за Ваш ответ. Я прочитал ваш ответ, и то, что вы сказали, абсолютно логично. Это моя ошибка из-за незнания API композиции Vue. Большое спасибо. Мне это действительно помогает. - person Leng Long; 25.10.2020