Vue3中输入元素在插槽内时属性响应性失效问题
表单验证包装类在插槽内的输入元素无法更新错误提示
我实现了一个用于表单验证的包装类Form,它接收带handler的数据对象,handler会执行校验并填充错误列表,类中包含获取特定字段错误信息的方法。另外我创建了一个带插槽的容器组件,插槽内放置使用v-model绑定值的表单元素(如input)。
预期效果是用户更新输入字段时,错误提示会随字段变化实时更新,但当输入元素放在插槽内时,错误提示不更新;如果把输入元素放在插槽外,或者在其他位置(如div)显示绑定的值,错误提示就能正常更新。
代码示例
Container.vue
<template> <div> Component with slot <div><slot></slot></div> </div> </template>
App.vue
<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>插槽内输入时错误提示不更新:</h1> <Container> <input type="text" v-model="data1.data.description"> </Container> <div> {{ data1.getErrors('description') }} </div> <h1>绑定值在其他地方使用时错误提示正常更新:</h1> <Container> <input type="text" v-model="data2.data.description"> </Container> <div>{{ data2.data.description }}</div> <div> {{ data2.getErrors('description') }} </div> <h1>输入不在插槽内时错误提示正常更新:</h1> <input type="text" v-model="data3.data.description"> <div> {{ data3.getErrors('description') }} </div> </template>
问题原因及解决方案
原因
Vue的响应式系统无法追踪DataForm类中errors数组的变化——它不是Vue的响应式对象。当输入在插槽内时,只有description的setter触发,但Vue未监听errors的变化,因此视图不会更新;而当你在其他地方使用data2.data.description时,Vue会追踪该属性并触发组件重新渲染,错误提示才会更新。
解决方案
将errors改为Vue的响应式对象,确保变化能被追踪。以下是两种可行方案:
方案1:用ref包裹errors
修改DataForm类,把errors定义为响应式ref:
import { ref } from 'vue' class DataForm { errors = ref<ErrorRecord[]>([]) // 转为响应式ref 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.value = [{key: 'description', error: `Too Short`}] // 访问ref的value属性 } else { this.errors.value = [] } } getErrors(path: string) { return this.errors.value.find(err => err.key == path)?.error // 访问ref的value属性 } }
方案2:用reactive包裹状态对象
将errors和_desc都放入reactive对象中,确保所有需要响应式的属性都被Vue追踪:
import { reactive } from 'vue' class DataForm { state = reactive<{ errors: ErrorRecord[], _desc: string }>({ errors: [], _desc: '' }) get data() { return { get description() {return this.state._desc}, set description(val) { this.state._desc = val; this.handler(val) } } } constructor() {} handler(value: any) { if (String(value).length < 5) { this.state.errors = [{key: 'description', error: `Too Short`}] } else { this.state.errors = [] } } getErrors(path: string) { return this.state.errors.find(err => err.key == path)?.error } }
修改后,无论输入元素是否在插槽内,错误提示都会随输入内容实时更新。
内容的提问来源于stack exchange,提问作者Anton Tikhonov
相关产品推荐
相关产品推荐

