Form组件的基本使用,外层是form组件,内部是form-item,两部分组成。相当于是两个组件,item是针对某一项,比如输入框进行校验,我们就可以使用form-item包裹起来,最后将所有的form-item包裹在form组件中。 我们应该先从内部开始设计组件。
-
k-form
- 载体,输入数据model,校验规则rules
- form实例上有校验validate函数
-
k-form-item
- label标签添加
- 载体,输入项包装
- 校验执行者,显示错误
文件结构
index.ts来整合表单组件,是入口。
FormItem Props定义
- prop:校验的输入框的属性
- label:输入框的标题
- rules:表单的验证规则,可参考 async-validator。
- show-message:是否显示错误,默认为true
- change / blur事件
注:需要本地安装async-validator,常见的规则都可以从导出的RuleItem看见,我们需要在此基础上扩展
// form-item.ts
import type { RuleItem } from 'async-validator'
import { ExtractPropTypes, InjectionKey, PropType } from 'vue'
export type Arrayable<T> = T | T[]
export const formItemValidateState = ['success', 'error', ''] as const
export type FormItemValidateState = (typeof formItemValidateState)[number]
export interface FormItemRule extends RuleItem {
trigger?: Arrayable<string>
}
export const formItemProps = {
prop: String,
label: String,
rules: [Object, Array] as PropType<Arrayable<FormItemRule>>,
showMessage: {
type: Boolean,
default: true
}
} as const
// Partial把属性变为可选的
export type FormItemProps = Partial<ExtractPropTypes<typeof formItemProps>>
Form-item结构实现
form-item.vue
<template>
<div :class="formItemClasses">
<!-- label属性 -->
<label :class="bem.e('label')">
<slot name="label">
{{ label }}
</slot></label
>
<!-- content盒⼦ -->
<div :class="bem.e('content')">
<!-- input -->
<slot></slot>
<!-- 错误信息 -->
<div :class="bem.e('error')">
<slot name="error">
{{ validateMessage }}
</slot>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { createNamespace } from '@kalin-ui/utils/create'
import { computed, ref } from 'vue'
import { formItemProps, FormItemValidateState } from './form-item'
const validateState = ref<FormItemValidateState>('error') // 校验状态
const validateMessage = ref('校验失败') // 错误消息
const bem = createNamespace('form-item')
const formItemClasses = computed(() => [
bem.b(),
bem.is('success', validateState.value ===
'success'),
bem.is('error', validateState.value ===
'error')
])
// DefineOptions 宏定义组件选项
defineOptions({
name: 'KFormItem'
})
const props = defineProps(formItemProps)
const shouldShowError = computed(() => {
// 当状态为失败并且需要显示错误消息时
return validateState.value === 'error' && props.showMessage
})
</script>
Form-item入口编写
import { withInstall } from '@kalin-ui/utils/with-install'
import _FormItem from './src/form-item.vue'
export const FormItem = withInstall(_FormItem)
export type FormInstance = InstanceType<typeof Form>
Form-item组件使用:
<script setup lang="ts">
import { ref } from 'vue'
const value = ref('')
</script>
<template>
<k-form-item label="username">
<k-input v-model="value"></k-input>
</k-form-item>
</template>
FromItem校验
<k-form-item
label="⽤户名"
prop="username"
:rules="[
{ required: true, message: '⽤户名必须填写', trigger: 'blur' },
{ min: 6, message: '⽤户名最少6位', trigger: 'change' }
]"
>
<k-input v-model="value"></k-input>
</k-form-item>
注:我们普通使用form-item组件的时候可能在外层套了很多层,比如row、col之类的布局组件,所以属性需要跨级传递,在vue3中使用provide/inject实现。
属性中进行定义:
export interface FormItemContext extends FormItemProps {
validate: (
trigger: string,
callback?: (isValid: boolean) => void
) => Promise<void>
}
export const FormItemContextKey: InjectionKey<FormItemContext> =
Symbol('form-item')
form-item.vue
根据对应的trigger类型过滤规则,在validate方法中,拿到触发的时机,校验是否通过可以调用callback 或者调用promise.then方法
const _rules = computed(() => {
const rules: FormItemRule[] = props.rules
? Array.isArray(props.rules)
? props.rules
: [props.rules]
: []
return rules
})
const getFilteredRule = (trigger: string) => {
const rules = _rules.value
return rules.filter(rule => {
if (!rule.trigger || !trigger) return true // 这种情况意味着无论如何都要校验
if (Array.isArray(rule.trigger)) {
return rule.trigger.includes(trigger)
} else {
return rule.trigger == trigger
}
})
}
const validate: FormItemContext['validate'] = async(trigger, callback?) => {
const rules = getFilteredRule(trigger)
console.log('校验', rules)
}
const context: FormItemContext = {
...props,
validate
}
provide('form-item', context)
input 严格来说并不属于form组件,但是input也需要做校验,当值改变的时候,需要通知他父级上的el-form-item做validate校验。 在input组件中触发校验,监控值的变化,触发blur事件
const formItem = inject(formItemContextKey)
watch(
() => props.modelValue,
() => {
formItem?.validate('change')
}
)
const handleBlur = (event: FocusEvent) => {
emit('blur', event)
formItem?.validate?.('blur')
}
测试:
重点
-
更多组件通讯方式get!以后别说只会父子通讯,插槽传结构,
ref获取组件实例等,还有祖孙级别(provide/inject),属性透传和事件监听($attrs/$listeners),获取父子组件实例($parent/$children) -
校验四要素的原理实现!如何获取对应字段的数据及规则,prop的作用
-
async-validator的使用!