英文:
resetting v-model of child component
问题
在我的父组件中,我有类似这样的东西,
<template>
<ProductCounter v-model="formData.productCount" label="product count" />
</template>
<script setup>
const initialFormData = {
productCount: null,
firstname: '',
surname: '',
phone: '',
email: '',
postcode: '',
submittedOnce: false,
errors: []
}
let formData = reactive({ ...initialFormData });
const clearUI = () => {
formData = reactive({ ...initialFormData });
triggerInlineForm.value = false;
}
</script>
我的子组件看起来是这样的,
<template>
<div class="form__row" @reset-counts="resetCount">
<div class="counter__row">
<label>{{ label }}</label>
<div class="form__counter">
<button class="form__button--decrease form__button--circle form__button--animate-scale" :disabled="value == 0 || props.disabled" @click.prevent="decreaseCount()">
<i>
<FontAwesomeIcon :icon="['fal', 'minus']" />
</i>
</button>
<input type="text" v-model="value" :disabled="props.disabled" @input="updateQty" placeholder="0"/>
<button class="form__button--increase form__button--circle form__button--animate-scale" :disabled="props.disabled" @click.prevent="increaseCount()">
<i>
<FontAwesomeIcon :icon="['fal', 'plus']" />
</i>
</button>
</div>
</div>
</div>
</template>
<script setup>
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
const emits = defineEmits(['update:modelValue', 'resetCounts']);
const props = defineProps({
label: {
type: String,
required: true
},
modelValue: {
type: String,
required: true,
default: 0
},
disabled: {
type: Boolean,
required: false
}
});
const value = ref(props.modelValue);
const updateQty = () => {
emits('update:modelValue', value.value)
}
const increaseCount = () => {
value.value++
emits('update:modelValue', value.value)
}
const decreaseCount = () => {
value.value--;
emits('update:modelValue', value.value)
}
</script>
英文:
In my parent component I have something similar to this,
<template>
<ProductCounter v-model="formData.productCount" label="product count" />
</template>
<script setup>
const initialFormData = {
productCount: null,
firstname: '',
surname: '',
phone: '',
email: '',
postcode: '',
submittedOnce: false,
errors: []
}
let formData = reactive({ ...initialFormData });
const clearUI = () => {
formData = reactive({ ...initialFormData });
triggerInlineForm.value = false;
}
</script>
My child component looks like this,
<template>
<div class="form__row" @reset-counts="resetCount">
<div class="counter__row">
<label>{{ label }}</label>
<div class="form__counter">
<button class="form__button--decrease form__button--circle form__button--animate-scale" :disabled="value == 0 || props.disabled" @click.prevent="decreaseCount()">
<i>
<FontAwesomeIcon :icon="['fal', 'minus']" />
</i>
</button>
<input type="text" v-model="value" :disabled="props.disabled" @input="updateQty" placeholder="0"/>
<button class="form__button--increase form__button--circle form__button--animate-scale" :disabled="props.disabled" @click.prevent="increaseCount()">
<i>
<FontAwesomeIcon :icon="['fal', 'plus']" />
</i>
</button>
</div>
</div>
</div>
</template>
<script setup>
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
const emits = defineEmits(['update:modelValue', 'resetCounts']);
const props = defineProps({
label: {
type: String,
required: true
},
modelValue: {
type: String,
required: true,
default: 0
},
disabled: {
type: Boolean,
required: false
}
});
const value = ref(props.modelValue);
const updateQty = () => {
emits('update:modelValue', value.value)
}
const increaseCount = () => {
value.value++
emits('update:modelValue', value.value)
}
const decreaseCount = () => {
value.value--;
emits('update:modelValue', value.value)
}
</script>
I would expect that when clearUI is fired from the parent, and formData gets reset the v-model of ProductCounter should reflect that go back to 0 but it does not, where am I going wrong?
答案1
得分: 2
请提前在 https://play.vuejs.org/ 上准备一个最小可复现示例。关于你的问题:
在 Vue 中,你不应该覆盖响应式变量,请改变它们,使用 Object.assign(formData, initialFormData)
:
同时不要解引用组件属性:const value = ref(props.modelValue)
。因为你只是复制了一个原始值,属性将失去响应性。
创建一个 v-model
模式的最佳方法是使用 computed
,你可以直接在模板中操纵它:
const value = computed({
get(){
return props.modelValue;
},
set(val){
emits('update:modelValue', val);
}
});
此外,你的 count
属性应该是一个数字,而不是字符串(这会导致 Vue 报警告):
modelValue: {
type: Number,
required: true,
default: 0
},
此外,在 input
事件上不需要更新 prop,因为你已经在 <input>
上使用了 v-model
。同时,你应该将输入的模型转换为数字:
<input type="text" v-model.number="value" :disabled="props.disabled" placeholder="0"/>
所以你的代码如下:
App.vue
<template>
<p>
<ProductCounter v-model="formData.productCount" label="product count" />
</p>
<button @click="clearUI">
Clear
</button>
<div>
{{ JSON.stringify(formData) }}
</div>
</template>
<script setup>
import ProductCounter from './ProductCounter.vue'
import { reactive } from 'vue'
const initialFormData = {
productCount: 0,
firstname: '',
surname: '',
phone: '',
email: '',
postcode: '',
submittedOnce: false,
errors: []
}
let formData = reactive({ ...initialFormData });
const clearUI = () => {
Object.assign(formData, initialFormData);
}
</script>
ProductCounter.vue:
<template>
<div class="form__row">
<div class="counter__row">
<label>{{ label }}</label>
<div class="form__counter">
<button :disabled="value == 0 || props.disabled" @click.prevent="value--">
-
</button>
<input type="text" v-model.number="value" :disabled="props.disabled" placeholder="0"/>
<button :disabled="props.disabled" @click.prevent="value++">
+
</button>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const emits = defineEmits(['update:modelValue']);
const props = defineProps({
label: {
type: String,
required: true
},
modelValue: {
type: Number,
required: true,
default: 0
},
disabled: {
type: Boolean,
required: false
}
});
const value = computed({
get(){
return props.modelValue;
},
set(val){
emits('update:modelValue', val);
}
});
</script>
英文:
Please prepare minimum reproducible example the next time on https://play.vuejs.org/. And to your question:
You SHOULD NOT overwrite reactive variables in Vue please...
Just mutate them Object.assign(formData, initialFormData)
:
Also don't dereference component properties: const value = ref(props.modelValue)
. The properties lose their reactivity because you just a copy a primitive value.
The best way to create a v-model
pattern is to use computed
which you can manipulate directly in the template.
const value = computed({
get(){
return props.modelValue;
},
set(val){
emits('update:modelValue', val);
}
});
Also your count property should be a number, not a string (you get Vue warnings):
modelValue: {
type: Number,
required: true,
default: 0
},
Also there's no need to update the prop on the input
event, since you're already using v-model
on the <input>
. Also you should convert your input's model to a number:
<input type="text" v-model.number="value" :disabled="props.disabled" placeholder="0"/>
So you have:
App.vue
<template>
<p>
<ProductCounter v-model="formData.productCount" label="product count" />
</p>
<button @click="clearUI">
Clear
</button>
<div>
{{ JSON.stringify(formData) }}
</div>
</template>
<script setup>
import ProductCounter from './ProductCounter.vue'
import {reactive} from 'vue'
const initialFormData = {
productCount: 0,
firstname: '',
surname: '',
phone: '',
email: '',
postcode: '',
submittedOnce: false,
errors: []
}
let formData = reactive({ ...initialFormData });
const clearUI = () => {
Object.assign(formData, initialFormData);
}
</script>
ProductCounter.vue:
<template>
<div class="form__row">
<div class="counter__row">
<label>{{ label }}</label>
<div class="form__counter">
<button :disabled="value == 0 || props.disabled" @click.prevent="value--">
-
</button>
<input type="text" v-model.number="value" :disabled="props.disabled" placeholder="0"/>
<button :disabled="props.disabled" @click.prevent="value++">
+
</button>
</div>
</div>
</div>
</template>
<script setup>
import {computed} from 'vue';
const emits = defineEmits(['update:modelValue']);
const props = defineProps({
label: {
type: String,
required: true
},
modelValue: {
type: Number,
required: true,
default: 0
},
disabled: {
type: Boolean,
required: false
}
});
const value = computed({
get(){
return props.modelValue;
},
set(val){
emits('update:modelValue', val);
}
});
</script>
答案2
得分: 1
当你在 clearUI()
中覆盖 formData
时,你改变了变量的内容:
let formData = reactive({ ...initialFormData });
const clearUI = () => {
formData = reactive({ ...initialFormData });
}
然而,这并不会改变在组件设置期间绑定到模板的对象。你可以使用 ref
并分配给它的值来解决这个问题:
const formData = ref({ ...initialFormData });
const clearUI = () => {
formData.value = { ...initialFormData };
}
或者你可以单独覆盖属性:
const formData = reactive({ ...initialFormData });
const clearUI = () => {
Object.assign(formData, initialFormData);
}
第二个问题是你在 ProductCounter
中将 value
设置为 props.modelValue
的初始值,但那是字面值,不是响应式属性。要修复它,你可以添加一个监听器:
const value = ref(props.modelValue);
watch(
() => props.modelValue,
() => value.value = props.modelValue
)
现在,当 props.modelValue
改变时,value
也会调整。
英文:
When you override formData
in clearUI()
, you change the content of the variable:
let formData = reactive({ ...initialFormData });
const clearUI = () => {
formData = reactive({ ...initialFormData });
}
However, this does not change the object that was bound to the template during component setup. You can fix this using ref
and assigning to its value:
const formData = ref({ ...initialFormData });
const clearUI = () => {
formData.value = { ...initialFormData };
}
or you can override the properties individually:
const formData = reactive({ ...initialFormData });
const clearUI = () => {
Object.assign(formData, initialFormData);
}
A second problem is that you are setting the value
in ProductCounter
to the initial value of props.modelValue
, but that is the literal value, not a reactive property. So when props.modelValue
changes, value
does not. To fix it, you can add a watcher:
const value = ref(props.modelValue);
watch(
() => props.modelValue,
() => value.value = props.modelValue
)
Now value
will be adjusted when props.modelValue
changes.
Here it is in a playground
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论