开发者问题收集

当输入元素位于插槽中时,属性的反应性在 Vue3 中不起作用

2024-01-16
175

我正在尝试实现一个用于验证表单的包装器类。这个想法是,这个 Form 类接收一个带有处理程序的数据对象,该处理程序执行必要的检查并填充错误列表。此类还有一个方法可以返回特定字段的错误列表。

此外,我还创建了一个容器组件,其中有一个插槽用于显示字段的标签。此组件定义了一个插槽,其中包含使用 v-model 指令绑定值的表单元素(例如输入)。

我的期望是,当用户更新输入字段时,如果出现错误,验证错误占位符会在字段更改时更新。但是当输入在插槽中时,不会发生这种情况。如果我将输入放在插槽之外或在另一个位置显示绑定值(例如 div 元素),则错误占位符会更新。

我创建了一个 最小示例 演示了该问题。

示例代码:

// Container.vue
<template>
    <div>
        Component with slot
        <div><slot></slot></div>
    </div>
</template>


// App
<script setup lang="ts">
import { ref } from 'vue'
import Container from './Container.vue';

type ErrorRecord = {key: string, error: string}

class DataForm {
    errors: ErrorRecord[] = []
    data = {
        _desc: '',
        get description() {return this._desc},
        set description(val) { this._desc = val; if (this.handler) { this.handler(val) } },
        handler: undefined as unknown as (val: any) => void
    }
    constructor() {
        this.data.handler = this.handler.bind(this)
    }
    handler(value: any) {
        if (String(value).length < 5) {
            this.errors = [{key: 'description', error: `Too Short`}]
        }
        else {
            this.errors = []
        }
    }
    getErrors(path: string) {
        return this.errors.find(err => err.key == path)?.error
    }    
}
const data1 = ref(new DataForm())
const data2 = ref(new DataForm())
const data3 = ref(new DataForm())


</script>

<template>
    <h1>Error not updated when input is in slot: </h1>
    <Container>
        <input type="text" v-model="data1.data.description">
    </Container>
    <div> {{ data1.getErrors('description') }} </div>

    <h1>Error updated when value is used somewhere else: </h1>
    <Container>
        <input type="text" v-model="data2.data.description">
    </Container>
    <div>{{ data2.data.description }}</div>
    <div> {{ data2.getErrors('description') }} </div>

    <h1>Error updated when input is not in slot: </h1>
    <input type="text" v-model="data3.data.description">
    <div> {{ data3.getErrors('description') }} </div>
</template>
2个回答

我更改了你的 最小示例 ,由于您在使用 ref 或 react 之前在 DataForm 类中做了很多事情,因此 vue 不会帮助您更新 dom,您需要先创建一个 ref,然后更新它。 此外,我给您留了一个问题,为什么数据使用 react 而不是 ref

moon
2024-01-17

将此作为答案发布,因为就我个人而言,原因并不明显。虽然从 JS/Vue 的角度来看这非常简单。我的主要经验是 C#。因此,我遵循了 OOP 模式。但在 Vue 中,反应对象是代理,所以在我的代码中,我引用了非反应性 this 。重写导致问题的代码以遵循建议的模式解决了问题。所以对于那些像我一样来自其他编程语言的人来说,这是一个很好的例子,不要盲目遵循我们在其他语言中习惯的模式。

SO 上的另一个例子: 链接

非常感谢@estus-flask。 链接 至更正后的工作示例。

工作代码:

// App.vue
<script setup lang="ts">
import { ref, reactive } from 'vue'
import Container from './Container.vue';

type ErrorRecord = {key: string, error: string}

const createForm = () => {
    var o = reactive({
        errors: [],
        data:{
            _desc: '',
            get description() {return o._desc},
            set description(val) { o._desc = val; o.data.handler(val) },
            handler(value: any) {
            if (String(value).length < 5) {
                o.errors = [{key: 'description', error: `Too Short`}]
            }
            else {
                o.errors = []
            }
        },
        },
        
        getErrors(path: string) {
            return o.errors.find(err => err.key == path)?.error
        }    
    })

    return o
}
const data1 = createForm()
const data2 = createForm()
const data3 = createForm()


</script>

<template>
    <h1>Error not updated when input is in slot: </h1>
    <Container>
        <input type="text" v-model="data1.data.description">
    </Container>
    <div> {{ data1.getErrors('description') }} </div>

    <h1>Error updated when value is used somewhere else: </h1>
    <Container>
        <input type="text" v-model="data2.data.description">
    </Container>
    <div>{{ data2.data.description }}</div>
    <div> {{ data2.getErrors('description') }} </div>

    <h1>Error updated when input is not in slot: </h1>
    <input type="text" v-model="data3.data.description">
    <div> {{ data3.getErrors('description') }} </div>
</template>
Anton Tikhonov
2024-01-18