前言
为了避免再被问住,咱就简单实现下吧!
先看下效果图
基本原理
基本思路:
表单校验有两种情景:1、整个表单form校验;2、表单项中input触发校验。因此整个表单校验核心是formitem的实现。只需将formitem中的validate方法提供给form和input,其余的事情如校验表单项、展示错误信息交给formitem去实现就好了。
核心概念:
通过provide/inject(响应式)来实现祖孙间通信;父组件provide传递;子孙组件inject接收
- test-form: 1、提供检验rules、model;2、收集表单项实例
- test-form-item: 1、提供prop并绑定model中对应的字段 2、提供校验方法validate 3、展示错误信息
- test-input: 值变化执行test-form-item中的校验方法
代码实现
Form
provide:props、addField职责:提供rules、model以及收集fields以供整个表单校验
<script setup>
import { provide, reactive,toRefs } from 'vue'
const props = defineProps(['model','rules'])
// 存储表单字段信息
let fields = reactive([])
const addField = (field) => {
fields.push(field)
}
// 表单整体检验方法
const validate = async (callback) => {
// 存储校验不通过信息
let validationErrors = {}
for (const field of fields) {
try {
await field.validate('')
} catch (fields) {
validationErrors = {
...validationErrors,
...fields
}
}
}
const result = Object.keys(validationErrors).length === 0
if (result) {
callback?.(result)
return Promise.resolve([result,null])
}
callback?.(result,validationErrors)
return Promise.resolve([result,validationErrors])
}
defineExpose({
validate
})
provide('formContentKey',
reactive({
...toRefs(props),
addField
})
)
</script>
<template>
<form>
<slot />
</form>
</template>
FormItem
inject: form组件中的props、addFieldprovide:props、validate职责:检验表单项、维护错误状态;通过addField给form提供validate,通过provide给input传递validate
<script setup>
import { provide, reactive, toRefs, onMounted, inject, ref, computed } from 'vue'
const formContext = inject('formContentKey', undefined)
const props = defineProps(['prop','required'])
const validateState = ref('')
const validateMessage = ref('')
// 表单项检验
const validate = async (trigger) => {
validateState.value = ''
const modelName = props.prop
const rules = formContext?.rules[modelName]
const fieldValue = formContext?.model[modelName]
// 根据rules进行校验(仅模拟)
rules.map(rule => {
if (validateState.value == 'error') return
// 匹配对应的触发条件
if (!(!trigger || rule.trigger.includes(trigger))) return
if (rule.required) {
if (fieldValue == '') {
validateState.value = 'error'
validateMessage.value = rule.message
}
} else if (rule.max && fieldValue.length > rule.max) {
validateState.value = 'error'
validateMessage.value = rule.message
} else {
validateState.value = 'success'
validateMessage.value = ''
}
})
if (validateState.value == 'success') return true
return Promise.reject({prop: modelName,validateState: validateState.value, validateMessage: validateMessage.value})
}
const showError = computed(() => validateState.value === 'error')
const context = reactive({
...toRefs(props),
validate,
validateState,
})
provide('formItemContextKey', context)
onMounted(() => {
if (props.prop) {
// 将当前的表单域添加到全局的表单中
formContext?.addField(context)
}
})
</script>
<template>
<div :class="[{ isError: showError}]">
<slot />
<div v-if="showError" class="error">{{ validateMessage }}</div>
</div>
</template>
<style>
.error {
color: red;
font-size: 12px;
position: absolute;
bottom: -20px;
}
.isError input{
border: 1px solid red;
}
</style>
Input
inject: 接收formitem的validate职责:数据有变化触发validate
<script setup>
import { reactive, inject, watch } from 'vue'
const formItemContext = inject('formItemContextKey', undefined)
const emits = defineEmits(['update:modelValue']);
const props = defineProps(['modelValue'])
const model = reactive({
inputValue: props.modelValue,
});
watch(
() => props.modelValue,
v => {
model.inputValue = v;
},
);
const onChange = (e) => {
emits('update:modelValue', e.target.value);
formItemContext.validate('change').catch(err => {});
}
</script>
<template>
<input @input="onChange" :value="model.inputValue" />
</template>
<style scoped>
input {
outline: 0;
}
</style>
Demo
<script setup>
import TestForm from './components/TestForm.vue'
import TestFormItem from './components/TestFormItem.vue'
import TestInput from './components/TestInput.vue'
import { reactive, ref } from 'vue'
const model = reactive({ input: 'too hot' })
const rules = reactive({
input: [
{ required: true, message: '请输入内容', trigger: 'change' },
{ max: 5, message: '最长 5 个字符', trigger: 'change' },
],
})
const formRef = ref()
const submit = async () => {
const [valid,fields] = await formRef.value.validate()
if (valid) alert('验证通过')
// formRef.value.validate((valid,fields)=>{
// if (valid) alert('验证通过')
// })
}
</script>
<template>
<div>
<TestForm ref="formRef" :model="model" :rules=rules>
<TestFormItem label="输入框" prop="input">
<TestInput v-model="model.input" />
</TestFormItem>
</TestForm>
<button style="margin-top: 20px" @click="submit">提交</button>
</div>
</template>