Vue 组合 API 在 v-for 中不是反应式的?

Vue composition API is not reactive inside 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>

这是我的 useInquiry 可组合逻辑

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-model 看起来不同步,最后一个输入总是被删除。

我觉得不错....除了 TextField.vue

问题出在 TextField.vue。你在 TextField.vuesetup 里面实际做的是这个 (Vue 2 API):

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

...这是inputValue 数据成员的一次性初始化,值为value 属性。因此,当删除其中一个 options 并重用组件时(因为 Vue 一直这样做——尤其是在 :key 中使用索引时) inputValue 不会更新为新值value 支持...

您根本不需要 inputValuemodel 选项,只需删除它并使用此模板:

<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 的自定义组件输入...

工作示例...

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/composition-api@1.0.0-beta.18"></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>