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.vue
的 setup
里面实际做的是这个 (Vue 2 API):
data() {
return {
inputValue: this.value
}
}
...这是inputValue
数据成员的一次性初始化,值为value
属性。因此,当删除其中一个 options
并重用组件时(因为 Vue 一直这样做——尤其是在 :key
中使用索引时) inputValue
不会更新为新值value
支持...
您根本不需要 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 的自定义组件输入...
工作示例...
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>
我正在使用 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.vue
的 setup
里面实际做的是这个 (Vue 2 API):
data() {
return {
inputValue: this.value
}
}
...这是inputValue
数据成员的一次性初始化,值为value
属性。因此,当删除其中一个 options
并重用组件时(因为 Vue 一直这样做——尤其是在 :key
中使用索引时) inputValue
不会更新为新值value
支持...
您根本不需要 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 的自定义组件输入...
工作示例...
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>