在Vant4的van-form组件中使用自定义Field输入框组件无法触发表单校验

1,432 阅读2分钟

自定义的组件大概如下:

<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方法导入到代理对象,否则会影响到正常的表单校验。