如何利用 Vue 的响应式系统和 `Composition API`,设计一个可扩展、可维护的表单校验库?

咳咳,各位观众老爷们,晚上好! 今天咱们不聊八卦,专心搞点技术。今天的主题是:如何利用 Vue 的响应式系统和 Composition API,打造一个既强大又灵活的表单校验库。

开场白:表单校验,前端老生常谈

说起表单校验,简直是前端程序员的家常便饭。 用户输入个手机号,你得看看是不是11位; 用户填个邮箱,你得瞅瞅格式对不对; 用户设个密码,你还得要求强度够不够。

传统的表单校验方式,代码散落在各处,耦合性高,复用性差,维护起来简直让人头大。 想象一下,改一个校验规则,你可能需要翻遍整个项目! 这简直比找对象还难!

所以,我们需要一套优雅、可扩展、可维护的表单校验方案,让我们的代码更清晰,让我们的生活更美好。

第一幕:Vue 的响应式魔法和 Composition API

要打造一个强大的表单校验库,首先要了解 Vue 的两大杀器:响应式系统和 Composition API。

1. Vue 的响应式系统:数据驱动一切

Vue 的响应式系统,简单来说,就是让数据变化自动驱动视图更新。 当我们修改一个响应式数据时,Vue 会自动追踪这个变化,并更新依赖于这个数据的组件。

在表单校验中,我们可以将表单数据定义为响应式数据,然后利用 Vue 的响应式系统,实时监听表单数据的变化,并进行校验。

2. Composition API:化繁为简的代码组织方式

Composition API 是 Vue 3 中引入的一种新的代码组织方式。 它可以让我们将组件的逻辑拆分成一个个独立的函数 (composables),然后在组件中组合使用。

与传统的 Options API 相比,Composition API 更加灵活、可复用、易于测试。 在表单校验库中,我们可以将不同的校验规则封装成独立的 composables,然后在组件中根据需要组合使用。

第二幕:校验库的设计思路

我们的目标是打造一个可扩展、可维护的表单校验库。 因此,我们需要考虑以下几个方面:

  1. 规则的定义和管理: 如何定义校验规则? 如何管理这些规则?
  2. 校验的触发时机: 何时触发校验? 是实时校验? 还是提交时校验?
  3. 错误信息的展示: 如何展示错误信息? 是在输入框下方显示? 还是统一显示?
  4. 可扩展性: 如何添加新的校验规则? 如何自定义错误信息?

1. 核心数据结构

首先,我们定义一个 ValidationSchema 类型,用于描述每个字段的校验规则:

interface ValidationSchema {
  [field: string]: {
    rules: ValidationRule[];
  };
}

interface ValidationRule {
  name: string;
  message?: string;
  validate: (value: any) => boolean | Promise<boolean>;
}
  • ValidationSchema 是一个对象,key 是字段名,value 是该字段的校验规则对象。
  • ValidationRule 包含三个属性:
    • name: 校验规则的名称,例如 requiredemail
    • message: 错误信息,可以自定义。
    • validate: 校验函数,接收字段的值作为参数,返回一个布尔值或 Promise,表示校验是否通过。

2. useValidation composable

接下来,我们创建一个 useValidation composable,用于处理表单校验的逻辑:

import { reactive, computed, watch } from 'vue';

export function useValidation(schema: ValidationSchema, initialValues: any = {}) {
  const values = reactive(initialValues);
  const errors = reactive<Record<string, string>>({});
  const touched = reactive<Record<string, boolean>>({});
  const isValid = computed(() => Object.keys(errors).length === 0);
  const isDirty = computed(() => Object.keys(touched).length > 0);

  const validateField = async (field: string) => {
    const rules = schema[field]?.rules || [];
    for (const rule of rules) {
      const isValid = await rule.validate(values[field]);
      if (!isValid) {
        errors[field] = rule.message || `Field ${field} is invalid.`;
        return;
      }
    }
    delete errors[field];
  };

  const validateAll = async () => {
    for (const field in schema) {
      await validateField(field);
    }
  };

  const handleInputChange = (field: string, value: any) => {
    values[field] = value;
    touched[field] = true;
    validateField(field); // 实时校验
  };

  const resetForm = () => {
    for (const field in values) {
      values[field] = initialValues[field] || '';
      delete errors[field];
      delete touched[field];
    }
  };

  watch(() => values, () => {
      // 监听所有值的变化,可以做一些全局性的逻辑
  }, { deep: true });

  return {
    values,
    errors,
    touched,
    isValid,
    isDirty,
    validateField,
    validateAll,
    handleInputChange,
    resetForm,
  };
}

这个 useValidation composable 接收两个参数:

  • schema: ValidationSchema 对象,描述了表单的校验规则。
  • initialValues: 表单的初始值,可选。

它返回以下属性和方法:

  • values: 响应式的表单数据对象。
  • errors: 响应式的错误信息对象,key 是字段名,value 是错误信息。
  • touched: 响应式的 touched 状态对象,key 是字段名,value 是布尔值,表示该字段是否被 touched。
  • isValid: 计算属性,表示表单是否有效。
  • isDirty: 计算属性,表示表单是否被修改过。
  • validateField: 用于校验单个字段的函数。
  • validateAll: 用于校验所有字段的函数。
  • handleInputChange: 用于处理输入框变化的函数,它会更新表单数据,并触发校验。
  • resetForm: 用于重置表单的函数。

3. 默认校验规则

为了方便使用,我们可以提供一些常用的校验规则:

const required: ValidationRule = {
  name: 'required',
  message: 'This field is required.',
  validate: (value: any) => !!value,
};

const email: ValidationRule = {
  name: 'email',
  message: 'Please enter a valid email address.',
  validate: (value: any) => {
    if (!value) return true; // 允许为空
    const regex = /^[^s@]+@[^s@]+.[^s@]+$/;
    return regex.test(value);
  },
};

const minLength = (length: number): ValidationRule => ({
  name: 'minLength',
  message: `This field must be at least ${length} characters.`,
  validate: (value: any) => !value || value.length >= length, // 允许为空
});

const maxLength = (length: number): ValidationRule => ({
  name: 'maxLength',
  message: `This field cannot be longer than ${length} characters.`,
  validate: (value: any) => !value || value.length <= length, // 允许为空
});

const pattern = (regex: RegExp, message: string): ValidationRule => ({
  name: 'pattern',
  message: message,
  validate: (value: any) => !value || regex.test(value), // 允许为空
});

// 可以根据需要添加更多规则,例如数字、URL、自定义规则等等

export const Rules = {
  required,
  email,
  minLength,
  maxLength,
  pattern,
};

这些规则都是一些简单的函数,接收字段的值作为参数,返回一个布尔值,表示校验是否通过。

第三幕:实战演练

现在,让我们用一个简单的例子来演示如何使用这个表单校验库。

<template>
  <div>
    <label for="name">Name:</label>
    <input type="text" id="name" v-model="validation.values.name" @blur="validation.touched.name = true">
    <div v-if="validation.touched.name && validation.errors.name" class="error">{{ validation.errors.name }}</div>

    <label for="email">Email:</label>
    <input type="email" id="email" v-model="validation.values.email" @blur="validation.touched.email = true">
    <div v-if="validation.touched.email && validation.errors.email" class="error">{{ validation.errors.email }}</div>

    <button @click="handleSubmit" :disabled="!validation.isValid">Submit</button>
    <button @click="validation.resetForm">Reset</button>
  </div>
</template>

<script setup lang="ts">
import { useValidation, Rules } from './validation'; // 假设 validation.ts 包含 useValidation 和 Rules

const schema = {
  name: {
    rules: [Rules.required, Rules.minLength(3), Rules.maxLength(10)],
  },
  email: {
    rules: [Rules.required, Rules.email],
  },
};

const validation = useValidation(schema, { name: '', email: '' });

const handleSubmit = async () => {
  await validation.validateAll();
  if (validation.isValid.value) {
    // 表单提交逻辑
    alert('Form is valid!');
  } else {
    alert('Form is invalid!');
  }
};
</script>

<style scoped>
.error {
  color: red;
}
</style>

在这个例子中,我们定义了一个包含 nameemail 两个字段的表单。

  • name 字段要求必填,且长度在 3 到 10 个字符之间。
  • email 字段要求必填,且必须是有效的邮箱地址。

我们使用 useValidation composable 来处理表单校验的逻辑。

  • v-model 指令将输入框的值绑定到 validation.values 对象上。
  • @blur 事件监听输入框失去焦点事件,并将对应的 validation.touched 属性设置为 true
  • v-if 指令用于显示错误信息。
  • handleSubmit 函数用于提交表单,它会先调用 validation.validateAll 函数校验所有字段,如果表单有效,则执行提交逻辑。
  • resetForm 函数用于重置表单。

第四幕:可扩展性设计

一个好的表单校验库,不仅要功能强大,还要易于扩展。 我们可以通过以下方式来提高校验库的可扩展性:

  1. 自定义校验规则: 允许用户自定义校验规则。
  2. 自定义错误信息: 允许用户自定义错误信息。
  3. 异步校验: 支持异步校验,例如从服务器端获取校验结果。
  4. 国际化支持: 支持国际化,可以显示不同语言的错误信息。

1. 自定义校验规则

用户可以通过 addRule 函数来添加自定义校验规则:

// 在 useValidation 内部
const addRule = (field: string, rule: ValidationRule) => {
  if (!schema[field]) {
    schema[field] = { rules: [] };
  }
  schema[field].rules.push(rule);
};

return {
  // ... 其他属性和方法
  addRule,
};

使用方法:

const validation = useValidation(schema, { name: '', email: '' });

validation.addRule('name', {
  name: 'custom',
  message: 'Name cannot contain numbers.',
  validate: (value: any) => !/d/.test(value),
});

2. 自定义错误信息

用户可以在定义校验规则时,自定义错误信息:

const schema = {
  name: {
    rules: [
      { ...Rules.required, message: 'Please enter your name.' },
      { ...Rules.minLength(3), message: 'Name must be at least 3 characters.' },
    ],
  },
  email: {
    rules: [
      { ...Rules.required, message: 'Please enter your email address.' },
      { ...Rules.email, message: 'Please enter a valid email address.' },
    ],
  },
};

3. 异步校验

如果校验规则需要从服务器端获取校验结果,可以使用异步校验。

const usernameAvailable: ValidationRule = {
  name: 'usernameAvailable',
  message: 'Username is not available.',
  validate: async (value: any) => {
    if (!value) return true; // 允许为空
    // 从服务器端获取校验结果
    const response = await fetch(`/api/check-username?username=${value}`);
    const data = await response.json();
    return data.available;
  },
};

4. 国际化支持

可以使用 Vue I18n 等国际化库来实现国际化支持。

第五幕:总结与展望

通过 Vue 的响应式系统和 Composition API,我们成功打造了一个可扩展、可维护的表单校验库。

这个校验库具有以下优点:

  • 代码清晰: 使用 Composition API 将校验逻辑封装成独立的 composables,代码更加清晰易懂。
  • 可复用性高: 校验规则可以复用在不同的组件中。
  • 易于测试: 独立的 composables 易于进行单元测试。
  • 可扩展性强: 可以方便地添加新的校验规则和自定义错误信息。

当然,这个校验库还有很多可以改进的地方,例如:

  • 更丰富的校验规则: 可以提供更多常用的校验规则,例如数字、URL、自定义规则等等。
  • 更灵活的错误信息展示方式: 可以提供更多的错误信息展示方式,例如在输入框上方显示、统一显示等等。
  • 更强大的异步校验功能: 可以支持取消异步校验请求、显示 loading 状态等等。

希望通过今天的讲座,能够帮助大家更好地理解 Vue 的响应式系统和 Composition API,并能够利用这些技术打造出更强大的表单校验库。

感谢各位的观看! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注