我有字段验证,在提交期间我需要关注有错误的第一个输入字段
从我的父组件 CustomForm 我无法访问 CustomInput 中的子组件输入
<script setup lang="ts">
import CustomForm from '@/components/CustomForm.vue'
</script>
<template>
<CustomForm />
</template>
<style scoped lang="scss">
</style>
<script setup lang="ts">
import CustomInput from '@/components/CustomInput.vue'
import { useForm } from '@/hooks/useForm'
const formData = [
{
name: 'title',
label: 'title',
required: true,
value: '',
isValid: false,
errorMessages: []
},
{
name: 'name',
label: 'name',
required: true,
type: 'textarea',
value: '',
isValid: false,
errorMessages: []
}
]
const { isFormValid, fieldsForm, submit } = useForm(formData)
const submitForm = () => {
submit()
if (isFormValid.value) {
console.log('submit')
console.log(fieldsForm)
}
}
</script>
<template>
<form @submit.prevent="submitForm">
<CustomInput v-for="data in fieldsForm"
:key="data.name"
ref="customInputRef"
:field-data="data"
v-model="data.value"
v-model:error="data.errorMessages"
v-model:isValid="data.isValid"
/>
<button type="submit">Отправить</button>
</form>
</template>
<style scoped lang="scss">
</style>
<script setup lang="ts">
import { computed, ref, watchEffect } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import type { IFieldProps } from '@/types/Field'
import { useInputValidator } from '@/hooks/useInputValidator'
const props = withDefaults(defineProps<IFieldProps>(), {
placeholder: (props: IFieldProps) => `Input ${props.fieldData.name.toUpperCase()} please`
})
const emit = defineEmits([
'update:modelValue',
'update:error',
'update:isValid',
])
const r= ref(null)
const inputComponent = ref(props.fieldData.type !== 'textarea' ? 'input' : 'textarea')
const inputId = computed(() => `input-${uuidv4()}`)
const { isBlurred,field, blurAction,inputAction, errors, isValid } = useInputValidator(props.fieldData)
const inputHandler = (e: Event) => {
const v = (e.target as HTMLInputElement).value
emit('update:modelValue', v)
inputAction()
emit('update:error', errors)
}
const blurHandler = (e: Event) => {
blurAction()
emit('update:error', errors)
emit('update:isValid', isValid)
}
</script>
<template>
<div class="field">
<label class="field__label"
:for="inputId">
{{ field.label }}
</label>
<div class="field__inner">
<div class="field-icon" v-if="$slots.icon">
<slot name="icon"></slot>
</div>
<component :is="inputComponent"
:name="field.name"
ref="r"
class="field__input"
:class="{
valid:field.isValid,
error:field.errorMessages.length,
}"
:id="inputId"
:value="field.value"
@input="inputHandler"
@blur="blurHandler"
:placeholder="props.placeholder" />
<template v-if="field.errorMessages">
<p class="field__error" v-for="(error,index) in field.errorMessages" :key="index">
{{ error }}
</p>
</template>
</div>
</div>
</template>
import type { Field } from '@/types/Field'
import { computed, ref } from 'vue'
import { validateField } from '@/helpers/validateField'
export const useForm = (formFields: Field[]) => {
const fieldsForm = ref(formFields)
const isFormValid = computed(() =>
fieldsForm.value.every(field => field.isValid)
)
const updateValidity = (fieldName: string, errors: string[]) => {
const field = fieldsForm.value.find(data => data.name === fieldName)
if (field) {
field.errorMessages = errors
}
}
const checkFields = () => {
fieldsForm.value.forEach(field => {
let err: string[] = []
if (field.required) {
if (!isFormValid.value && !field.errorMessages.length) {
err = validateField(field.name, field.value)
updateValidity(field.name, err)
}
}
})
}
const submit = () => {
checkFields()
}
return {
submit,
isFormValid,
fieldsForm
}
}
import { computed, ref, watchEffect } from 'vue'
import type { Field } from '@/types/Field'
import { validateField } from '@/helpers/validateField'
export const useInputValidator = (fieldForm: Field) => {
const field = ref<Field>(fieldForm)
const errors = ref(field.value.errorMessages)
const isBlurred = ref(false)
const isValid = computed(() => {
return !errors.value.length
})
watchEffect(()=>{
if(field.value.errorMessages.length){
isBlurred.value= true
errors.value= field.value.errorMessages
}
})
const inputAction = () => {
if (isBlurred.value) {
errors.value = validateField(field.value.name, field.value.value)
}
}
const blurAction = () => {
if (isBlurred.value) return
errors.value = validateField(field.value.name, field.value.value)
isBlurred.value =true
}
return {
field,
isValid,
errors,
blurAction,
inputAction,
isBlurred
}
}
我验证了这些字段。但重点关注的功能仍然存在。我想通过钩子访问表单字段 如何更改主数组的数据,例如isValid? ...................................................... ...................................................... ..
首先,要根据文档应用v-for内的引用,请使用数组或函数。
其次,在子组件中,使用
defineExpose()
绑定到所需元素组件引用。
我创建了简单的示例:
/* App.vue */
<script setup>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
const formData = ref({
firstName: '',
lastName: '',
})
const inputs = ref({});
const focus = (key) => {
inputs.value[key].input.focus();
}
</script>
<template>
<div class="inputs">
<CustomInput
v-for="(value, key) in formData"
v-model="formData[key]"
:ref="el => inputs[key] = el"
:label="key"
></CustomInput>
</div>
<br>
<div class="btns">
<button
v-for="(value, key) in inputs"
@click="focus(key)"
>
Focus to {{ key }}
</button>
</div>
<br>
<pre>{{ formData }}</pre>
</template>
<style>
.inputs {
display: grid;
gap: 4px;
}
.btns {
display: flex;
gap: 8px;
}
</style>
/* CustomInput.vue */
<script setup>
import { ref } from 'vue';
defineProps({
label: String,
})
const model = defineModel();
const input = ref();
defineExpose({ input });
</script>
<template>
<label>
{{ label }}:
<br>
<input
ref="input"
v-model="model"
>
</label>
</template>