自定义的组件大概如下:
<template>
<van-field v-model="" :rules="[{ required: true, message: '请选择' + $attrs.label }]" required>
</van-field>
</template>
其中有一个van-field组件,且定义了rules属性,正常来讲van-form触发submit事件时会先执行所有van-field的validate方法,具体源码如下:
\node_modules\vant\es\form\Form.mjs
// 1.触发onSubmit
const onSubmit = (event) => {
preventDefault(event);
submit();
};
// 2.执行submit方法
const submit = () => {
const values = getValues();
validate().then(() => emit("submit", values)).catch((errors) => {
emit("failed", {
values,
errors
});
if (props.scrollToError && errors[0].name) {
scrollToField(errors[0].name);
}
});
};
// 3.执行validate方法
const validate = (name2) => {
if (typeof name2 === "string") {
return validateField(name2);
}
return props.validateFirst ? validateSeq(name2) : validateAll(name2);
};
// 4.执行validateAll方法
const validateAll = (names) => new Promise((resolve, reject) => {
const fields = getFieldsByNames(names);
Promise.all(fields.map((item) => item.validate())).then((errors) => {
errors = errors.filter(Boolean);
if (errors.length) {
reject(errors);
} else {
resolve();
}
});
});
validateAll方法中通过getFieldsByNames方法获取所有注册到当前form的子组件,然后异步执行所有子组件的validate方法,由此可见,当此处获取到的子组件对象不存在validate时会影响到正常的表单校验功能,所以需要进一步探究子组件是如何注册到当前form的,源码如下:
\node_modules\vant\es\form\Form.mjs
const {children,linkChildren} = useChildren(FORM_KEY);
...
linkChildren({props});
\node_modules\vant\es\field\Field.mjs
const {parent: form} = useParent(FORM_KEY);
...
useExpose({
blur,
focus,
validate,
formValue,
resetValidation,
getValidationStatus
});
\node_modules\@vant\use\dist\index.esm.mjs
function useChildren(key) {
const publicChildren = reactive([]);
const internalChildren = reactive([]);
const parent = getCurrentInstance2();
const linkChildren = (value) => {
const link = (child) => {
if (child.proxy) {
internalChildren.push(child);
publicChildren.push(child.proxy);
sortChildren(parent, publicChildren, internalChildren);
}
};
const unlink = (child) => {
const index = internalChildren.indexOf(child);
publicChildren.splice(index, 1);
internalChildren.splice(index, 1);
};
provide(
key,
Object.assign(
{
link,
unlink,
children: publicChildren,
internalChildren
},
value
)
);
};
return {
children: publicChildren,
linkChildren
};
}
function useParent(key) {
const parent = inject(key, null);
if (parent) {
const instance = getCurrentInstance();
const { link, unlink, internalChildren } = parent;
link(instance);
onUnmounted(() => unlink(instance));
const index = computed(() => internalChildren.indexOf(instance));
return {
parent,
index
};
}
return {
parent: null,
index: ref2(-1)
};
}
\node_modules\vant\es\composables\use-expose.mjs
function useExpose(apis) {
const instance = getCurrentInstance();
if (instance) {
extend(instance.proxy, apis);
}
}
form组件在setup中执行useChildren方法,对后代组件提供自身的props以及link、unlink、子组件代理对象数组(children)、子组件对象数据(internalChildren)。
field组件在setup中执行useParent方法,注入link方法,通过执行link方法将自身对象放入子组件对象数据(internalChildren),自身对象的代理对象即在validateAll中执行validate方法的对象放入子组件代理对象数组(children)。
field组件在setup中还会执行useExpose方法,这样就可以将validate方法导入到field组件对象的代理对象,如此form的表单验证功能所有关键代码均已完成。
在van-form中使用带有van-field的自定义组件,如果自定义组件没有使用useParent方法,即没有通过link方法将自身的代理对象放入子组件代理对象数组,则在表单校验的时候会直接忽略自定义组件,而是直接执行自定义组件中的van-field的校验方法。如果自定义组件使用useParent方法,则必须使用useExpose方法将validate方法导入到代理对象,否则会影响到正常的表单校验。