Vue3自定义组件中ref与v-for结合及表单聚焦、数据更新问题
Vue3 开发问题解决方案
1. 自定义组件中同时使用 ref 和 v-for
在 v-for 中给自定义组件绑定 ref,需要通过函数式 ref收集实例,避免直接绑定变量导致的覆盖问题。同时组件内部要通过 defineExpose 暴露需要访问的属性或方法。
示例代码:
<template> <div> <CustomComponent v-for="item in list" :key="item.id" :ref="el => { if (el) componentRefs.push(el) }" :data="item" /> </div> </template> <script setup> import { ref, onMounted } from 'vue' import CustomComponent from './CustomComponent.vue' const list = ref([{ id: 1 }, { id: 2 }, { id: 3 }]) const componentRefs = ref([]) onMounted(() => { // 访问第一个组件实例 console.log(componentRefs.value[0]) }) </script>
自定义组件 CustomComponent.vue 需暴露内部内容:
<script setup> import { ref } from 'vue' const inputRef = ref(null) // 暴露给父组件访问 defineExpose({ inputRef, focus: () => inputRef.value.focus() }) </script>
2. 提交时聚焦第一个错误输入框 + 父组件访问子组件元素
核心思路:子组件暴露内部输入框的 ref 和聚焦方法,父组件收集所有子组件实例,验证后找到第一个错误项并调用聚焦方法。
步骤1:子组件 CustomInput.vue 暴露输入元素
<template> <input ref="inputRef" v-model="localValue" :class="{ 'error': !field.isValid }" /> </template> <script setup> import { ref, computed } from 'vue' const props = defineProps(['field']) const emit = defineEmits(['update:field']) const inputRef = ref(null) const localValue = computed({ get: () => props.field.value, set: val => emit('update:field', { ...props.field, value: val }) }) // 暴露输入框ref和聚焦方法 defineExpose({ inputRef, focus: () => inputRef.value?.focus(), get isValid() { return props.field.isValid } }) </script>
步骤2:父组件 CustomForm.vue 收集实例并处理提交
<template> <form @submit.prevent="handleSubmit"> <CustomInput v-for="field in formFields" :key="field.name" :ref="el => { if (el) inputRefs.push(el) }" :field="field" @update:field="updateField" /> <button type="submit">提交</button> </form> </template> <script setup> import { ref } from 'vue' import CustomInput from './CustomInput.vue' const formFields = ref([ { name: 'username', value: '', isValid: true, error: '' }, { name: 'email', value: '', isValid: true, error: '' } ]) const inputRefs = ref([]) const updateField = (updatedField) => { const index = formFields.value.findIndex(f => f.name === updatedField.name) if (index !== -1) { formFields.value[index] = updatedField } } const validateFields = () => { // 模拟验证逻辑 formFields.value.forEach(field => { if (!field.value.trim()) { field.isValid = false field.error = '不能为空' } else { field.isValid = true field.error = '' } }) // 返回是否全部有效 return formFields.value.every(f => f.isValid) } const handleSubmit = () => { const isAllValid = validateFields() if (!isAllValid) { // 找到第一个错误的输入框并聚焦 const firstErrorInput = inputRefs.value.find(input => !input.isValid) firstErrorInput?.focus() } else { // 提交逻辑 console.log('表单提交') } } </script>
3. 从 Hooks 访问表单字段并修改主数组数据
将响应式的表单字段数组传入 Hooks,直接在 Hooks 中修改数组元素的属性(ref 包裹的数组是响应式的,修改内部元素会触发更新)。
定义自定义 Hook useFormValidation.js
import { watch } from 'vue' export function useFormValidation(fieldsRef) { // 监听字段变化自动验证 watch( () => fieldsRef.value.map(f => f.value), () => validateAllFields(), { deep: true } ) // 手动触发全量验证 const validateAllFields = () => { fieldsRef.value.forEach(field => { if (!field.value.trim()) { field.isValid = false field.error = '不能为空' } else if (field.name === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(field.value)) { field.isValid = false field.error = '邮箱格式错误' } else { field.isValid = true field.error = '' } }) return fieldsRef.value.every(f => f.isValid) } // 单个字段验证 const validateField = (fieldName) => { const field = fieldsRef.value.find(f => f.name === fieldName) if (!field) return if (!field.value.trim()) { field.isValid = false field.error = '不能为空' } else { field.isValid = true field.error = '' } } return { validateAllFields, validateField } }
在组件中使用 Hook
<script setup> import { ref } from 'vue' import { useFormValidation } from './useFormValidation.js' const formFields = ref([ { name: 'username', value: '', isValid: true, error: '' }, { name: 'email', value: '', isValid: true, error: '' } ]) const { validateAllFields, validateField } = useFormValidation(formFields) const handleSubmit = () => { const isAllValid = validateAllFields() if (isAllValid) { console.log('提交表单') } } </script>
内容的提问来源于stack exchange,提问作者Славик Гусев
相关产品推荐
相关产品推荐

