重置子组件的 v-model

huangapple go评论64阅读模式
英文:

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,

&lt;template&gt;
    &lt;ProductCounter v-model=&quot;formData.productCount&quot; label=&quot;product count&quot; /&gt;
&lt;/template&gt;

&lt;script setup&gt;

const initialFormData = {
    productCount: null,
    firstname: &#39;&#39;,
    surname: &#39;&#39;,
    phone: &#39;&#39;,
    email: &#39;&#39;,
    postcode: &#39;&#39;,
    submittedOnce: false,
    errors: []
}

let formData = reactive({ ...initialFormData });

const clearUI = () =&gt; {
    formData = reactive({ ...initialFormData });
    triggerInlineForm.value = false;
}

&lt;/script&gt;

My child component looks like this,

&lt;template&gt;
    &lt;div class=&quot;form__row&quot; @reset-counts=&quot;resetCount&quot;&gt;
        &lt;div class=&quot;counter__row&quot;&gt;
            &lt;label&gt;{{ label }}&lt;/label&gt;
            &lt;div class=&quot;form__counter&quot;&gt;
                &lt;button class=&quot;form__button--decrease form__button--circle form__button--animate-scale&quot; :disabled=&quot;value == 0 || props.disabled&quot; @click.prevent=&quot;decreaseCount()&quot;&gt;
                    &lt;i&gt;
                        &lt;FontAwesomeIcon :icon=&quot;[&#39;fal&#39;, &#39;minus&#39;]&quot; /&gt;
                    &lt;/i&gt;
                &lt;/button&gt;
                &lt;input type=&quot;text&quot; v-model=&quot;value&quot; :disabled=&quot;props.disabled&quot; @input=&quot;updateQty&quot; placeholder=&quot;0&quot;/&gt;
                &lt;button class=&quot;form__button--increase form__button--circle form__button--animate-scale&quot; :disabled=&quot;props.disabled&quot; @click.prevent=&quot;increaseCount()&quot;&gt;
                    &lt;i&gt;
                        &lt;FontAwesomeIcon :icon=&quot;[&#39;fal&#39;, &#39;plus&#39;]&quot; /&gt;
                    &lt;/i&gt;
                &lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { FontAwesomeIcon } from &#39;@fortawesome/vue-fontawesome&#39;;

const emits = defineEmits([&#39;update:modelValue&#39;, &#39;resetCounts&#39;]);

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 = () =&gt; {
    emits(&#39;update:modelValue&#39;, value.value)
}

const increaseCount = () =&gt; {
    value.value++
    emits(&#39;update:modelValue&#39;, value.value)
}

const decreaseCount = () =&gt; {
    value.value--;
    emits(&#39;update:modelValue&#39;, value.value)
}

&lt;/script&gt;

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>
英文:

The link to live solution

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(&#39;update:modelValue&#39;, 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 &lt;input&gt;. Also you should convert your input's model to a number:

 &lt;input type=&quot;text&quot; v-model.number=&quot;value&quot; :disabled=&quot;props.disabled&quot; placeholder=&quot;0&quot;/&gt;

So you have:
App.vue

&lt;template&gt;
  	&lt;p&gt;
  	  &lt;ProductCounter v-model=&quot;formData.productCount&quot; label=&quot;product count&quot; /&gt;
    &lt;/p&gt;
    &lt;button @click=&quot;clearUI&quot;&gt;
      Clear
  	&lt;/button&gt;
    &lt;div&gt;
      {{ JSON.stringify(formData) }}
  &lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import ProductCounter from &#39;./ProductCounter.vue&#39;
import {reactive} from &#39;vue&#39;
  
const initialFormData = {
    productCount: 0,
    firstname: &#39;&#39;,
    surname: &#39;&#39;,
    phone: &#39;&#39;,
    email: &#39;&#39;,
    postcode: &#39;&#39;,
    submittedOnce: false,
    errors: []
}

let formData = reactive({ ...initialFormData });

const clearUI = () =&gt; {
    Object.assign(formData, initialFormData);
}

&lt;/script&gt;

ProductCounter.vue:

&lt;template&gt;
    &lt;div class=&quot;form__row&quot;&gt;
        &lt;div class=&quot;counter__row&quot;&gt;
            &lt;label&gt;{{ label }}&lt;/label&gt;
            &lt;div class=&quot;form__counter&quot;&gt;
                &lt;button :disabled=&quot;value == 0 || props.disabled&quot; @click.prevent=&quot;value--&quot;&gt;
                -
                &lt;/button&gt;
                &lt;input type=&quot;text&quot; v-model.number=&quot;value&quot; :disabled=&quot;props.disabled&quot; placeholder=&quot;0&quot;/&gt;
                &lt;button :disabled=&quot;props.disabled&quot; @click.prevent=&quot;value++&quot;&gt;
                 +
                &lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import {computed} from &#39;vue&#39;;
const emits = defineEmits([&#39;update:modelValue&#39;]);

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(&#39;update:modelValue&#39;, val);
  }
});


&lt;/script&gt;

答案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 = () =&gt; {
    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 = () =&gt; {
    formData.value = { ...initialFormData };
}

or you can override the properties individually:

const formData = reactive({ ...initialFormData });

const clearUI = () =&gt; {
    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(
  () =&gt; props.modelValue,
  () =&gt; value.value = props.modelValue
)

Now value will be adjusted when props.modelValue changes.

Here it is in a playground

huangapple
  • 本文由 发表于 2023年6月19日 17:55:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76505519.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定