vue 3.0.0 + typescript 3.9.3 自定义校验表单( 父子组件通信 )
-
首先我们先把表单创建出来
ValidateInput.vue
<template> <div class="validate-input-container"> <input class="form-control" v-model="inputItem" /> <span class="invalid-feedback" > 错误信息 </span> </div> </template> <script lang="ts"> import { defineComponent, reactive } from 'vue' export default defineComponent({ setup(props, context){ const inputItem = reactive({ val: '', error: false, message: '' }) return { inputItem } } }) </script>
ValidateForm.vue
<template> <!-- vue3.0 slot插槽使用 和 vue2.0 没什么差别 --> <div class="validate-form-container"> <slot name="default"></slot> <div class="submit-area" @click.prevent="submitForm" > <slot name="submit"> <button type="submit" class="btn btn-primary" > 提交 </button> </slot> </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ setup (props, context) { const submitForm = () => { console.log('test') } return { submitForm } } }) </script>
Login.vue
<template> <div class="login-page mx-auto p-3 w-330"> <h5 class="my-4 text-center">登录</h5> <validate-form> <div class="mb-3"> <label for="exampleInputEmail1" class="form-label" >邮箱地址</label> <validate-input></validate-input> </div> <div class="mb-3"> <label for="exampleInputEmail1" class="form-label" >密码</label> <validate-input></validate-input> </div> </validate-form> </div> </template> <script lang='ts'> import { defineComponent, ref} from 'vue' import ValidateForm from './../components/ValidateForm.vue' import ValidateInput from './../components/ValidateInput.vue' export default defineComponent({ components: { ValidateForm, ValidateInput }, setup () { const loginForm = ref({ emailVal: '', passVal: '' }) return { loginForm } } }) </script> <style scoped> </style>
-
实现自定义 v-model
props:{ modelValue: String }, setup(props,context){ const inputRef = reactive({ val: computed({ get: () => props.modelValue || '', set: val => { context.emit('update:modelValue', val) } }), error: false, message: '' }) }
******* Login.vue ValidateInput组件中 v-model="loginForm.emailVal" v-model="loginForm.passVal" 测试是否成功 <p>{{loginForm.passVal}}</p> *******
-
接着是 禁用 Attribute 继承 可以访问组件的
$attrs
property,该 property 包括组件props
和emits
property 中未包含的所有属性 (例如,class
、style
、v-on
监听器等)。******* Login.vue 注意设置了 placeholder="请输入密码" type="password" ******* <validate-input ref="inputRef" placeholder="请输入密码" type="password" v-model="loginForm.passVal" ></validate-input>
******* ValidateInput.vue 禁用 Attribute 继承 inheritAttrs: false ,接着需要将所有非 prop attribute 应用于 input 元素而不是根 div 元素,则可以使用 v-bind 缩写来完成。 ******* <template> <div class="validate-input-container"> <input class="form-control" v-bind="$attrs" v-model="inputRef.val" /> </div> </template> props: { modelValue: String }, inheritAttrs: false
然后我们看一下效果
-
ValidateForm 需要得到 ValidateInput 校验是否有效 的结果 但是 ValidateForm ( 由于slot ) 无法使用
context.emit
需要使用类似vue2.x
的事件总线去处理 这里用到一个插件mitt
-
最后贴一份整体的代码
ValidateInput.vue
<template> <div class="validate-input-container"> <input class="form-control" v-bind="$attrs" @blur="checkFormVal" v-model="inputRef.val" :class="{'is-invalid': inputRef.error}" /> <span v-if="inputRef.error" class="invalid-feedback" > {{inputRef.message}} </span> </div> </template> <script lang='ts'> import { defineComponent, reactive, PropType, computed, onMounted } from 'vue' import mitt from 'mitt' const emailReg = \/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$\/ interface RuleProp { type: 'required' | 'email'; message: string; } export type RulesProps = RuleProp[] export const emitter = mitt() export default defineComponent({ props: { rules: Array as PropType<RulesProps>, modelValue: String }, inheritAttrs: false, setup (props, context) { const inputRef = reactive({ val: computed({ get: () => props.modelValue || '', set: val => { context.emit('update:modelValue', val) } }), error: false, message: '' }) // const changeInputValue = ( e: KeyboardEvent ) => { // const targetValue = (e.target as HTMLInputElement).value // inputRef.val = targetValue // context.emit('update:modelValue',targetValue) // } const checkFormVal = () => { if (props.rules) { const allPassed = props.rules.every(rule => { let passed = true inputRef.message = rule.message switch (rule.type) { case 'required': passed = inputRef.val.trim() !== '' break; case 'email': passed = emailReg.test(inputRef.val) break; default: break; } return passed }) inputRef.error = !allPassed return allPassed } return true } onMounted(() => { emitter.emit('form-item-created', checkFormVal) }) return { inputRef, checkFormVal } } }) </script> <style scoped> </style>
ValidateForm.vue
<template> <div class="validate-form-container"> <slot name="default"></slot> <div class="submit-area" @click.prevent="submitForm" > <slot name="submit"> <button type="submit" class="btn btn-primary" > 提交 </button> </slot> </div> </div> </template> <script lang='ts'> import { defineComponent, onUnmounted } from 'vue' import { emitter } from './ValidateInput.vue' type ValidateFunc = () => boolean export default defineComponent({ setup (props, context) { const funcArr: ValidateFunc[] = [] const submitForm = () => { const result = funcArr.map(func => func()).every(result => result) context.emit('form-submit', result) } const callback = (func?: ValidateFunc) => { if (func) { funcArr.push(func) } } emitter.on('form-item-created', callback) onUnmounted(() => { emitter.off('form-item-created', callback) }) return { submitForm } } }) </script> <style scoped> </style>
Login.vue
<template> <div class="login-page mx-auto p-3 w-330"> <h5 class="my-4 text-center">登录</h5> <validate-form @form-submit="onFormSubmit"> <div class="mb-3"> <label for="exampleInputEmail1" class="form-label" >邮箱地址</label> <validate-input ref="inputRef" placeholder="请输入邮箱地址" type="text" v-model="loginForm.emailVal" :rules="emailRule" ></validate-input> <p>{{loginForm.emailVal}}</p> </div> <div class="mb-3"> <label for="exampleInputEmail1" class="form-label" >密码</label> <validate-input ref="inputRef" placeholder="请输入密码" type="password" v-model="loginForm.passVal" :rules="passRule" ></validate-input> <p>{{loginForm.passVal}}</p> </div> </validate-form> </div> </template> <script lang='ts'> import { defineComponent, ref} from 'vue' import ValidateForm from './../components/ValidateForm.vue' import ValidateInput, { RulesProps } from './../components/ValidateInput.vue' export default defineComponent({ components: { ValidateForm, ValidateInput }, setup () { const loginForm = ref({ emailVal: '', passVal: '' }) const emailRule: RulesProps = [ { type: 'required', message: '请输入邮箱' }, { type: 'email', message: '请输入正确的邮箱地址' } ] const passRule: RulesProps = [ { type: 'required', message: '请输入密码' } ] const onFormSubmit = (result: boolean) => { if (result) { alert('success') } } return { loginForm, emailRule, passRule, onFormSubmit } } }) </script> <style scoped> </style>