You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

借助Yup实现前后端Schema校验同步及JSON复用可行性咨询

完全可以实现这种前后端复用校验Schema的方案

这是前后端统一校验逻辑的常用实践,既能避免重复编写校验规则,又能保证前后端校验逻辑一致。下面是具体的实现步骤和代码示例:

1. 定义JSON格式的通用校验Schema

先设计一套能映射Yup常用规则的JSON结构,覆盖必填、类型、长度、格式等常见校验场景。示例如下:

{
  "email": {
    "type": "string",
    "required": true,
    "email": true,
    "label": "邮箱"
  },
  "password": {
    "type": "string",
    "required": true,
    "min": 6,
    "max": 20,
    "label": "密码"
  },
  "age": {
    "type": "number",
    "required": false,
    "min": 18,
    "label": "年龄"
  }
}

2. 后端:将JSON Schema转为Yup并校验

编写通用转换函数,把JSON规则映射为Yup的Schema对象,然后在Express路由中用于校验请求体。

转换函数示例

const yup = require('yup');

function jsonToYupSchema(jsonSchema) {
  const yupSchema = {};
  for (const [field, rules] of Object.entries(jsonSchema)) {
    let yupField;
    // 根据类型初始化Yup字段
    switch (rules.type) {
      case 'string':
        yupField = yup.string();
        break;
      case 'number':
        yupField = yup.number();
        break;
      case 'boolean':
        yupField = yup.boolean();
        break;
      default:
        yupField = yup.mixed();
    }

    // 绑定校验规则
    if (rules.required) {
      yupField = yupField.required(`${rules.label}不能为空`);
    }
    if (rules.min !== undefined) {
      yupField = yupField.min(rules.min, `${rules.label}不能小于${rules.min}`);
    }
    if (rules.max !== undefined) {
      yupField = yupField.max(rules.max, `${rules.label}不能大于${rules.max}`);
    }
    if (rules.email) {
      yupField = yupField.email(`${rules.label}格式不正确`);
    }
    // 可扩展更多规则:如matches正则、oneOf枚举等

    yupSchema[field] = yupField;
  }
  return yup.object().shape(yupSchema);
}

Express路由使用示例

const express = require('express');
const router = express.Router();

// 存储所有校验Schema(可替换为数据库读取)
const validationSchemas = {
  userRegistration: require('./schemas/user-registration.json')
};

// 提供接口返回JSON Schema给前端
router.get('/validation-schemas/:schemaName', (req, res) => {
  const { schemaName } = req.params;
  const schema = validationSchemas[schemaName];
  if (!schema) {
    return res.status(404).json({ error: 'Schema不存在' });
  }
  res.json(schema);
});

// 注册接口:用Yup校验请求体
router.post('/register', async (req, res) => {
  const yupSchema = jsonToYupSchema(validationSchemas.userRegistration);
  try {
    await yupSchema.validate(req.body, { abortEarly: false });
    // 校验通过,执行注册逻辑
    res.status(200).json({ message: '注册成功' });
  } catch (err) {
    // 收集所有错误信息返回
    const errors = err.inner.reduce((acc, curr) => {
      acc[curr.path] = curr.message;
      return acc;
    }, {});
    res.status(400).json({ errors });
  }
});

module.exports = router;

3. 前端:获取JSON Schema并转为Yup复用

前端请求后端接口拿到JSON Schema后,用相同的转换函数转为Yup对象,配合Formik等表单库实现前端校验。

React + Formik示例

import { useState, useEffect } from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import yup from 'yup';

// 复用后端的转换函数(可抽为公共工具文件)
function jsonToYupSchema(jsonSchema) {
  const yupSchema = {};
  for (const [field, rules] of Object.entries(jsonSchema)) {
    let yupField;
    switch (rules.type) {
      case 'string':
        yupField = yup.string();
        break;
      case 'number':
        yupField = yup.number();
        break;
      case 'boolean':
        yupField = yup.boolean();
        break;
      default:
        yupField = yup.mixed();
    }

    if (rules.required) {
      yupField = yupField.required(`${rules.label}不能为空`);
    }
    if (rules.min !== undefined) {
      yupField = yupField.min(rules.min, `${rules.label}不能小于${rules.min}`);
    }
    if (rules.max !== undefined) {
      yupField = yupField.max(rules.max, `${rules.label}不能大于${rules.max}`);
    }
    if (rules.email) {
      yupField = yupField.email(`${rules.label}格式不正确`);
    }

    yupSchema[field] = yupField;
  }
  return yup.object().shape(yupSchema);
}

function RegisterForm() {
  const [validationSchema, setValidationSchema] = useState(null);

  useEffect(() => {
    // 请求后端获取校验Schema
    fetch('/api/validation-schemas/userRegistration')
      .then(res => res.json())
      .then(jsonSchema => {
        const yupSchema = jsonToYupSchema(jsonSchema);
        setValidationSchema(yupSchema);
      })
      .catch(err => console.error('获取校验Schema失败:', err));
  }, []);

  if (!validationSchema) {
    return <div>加载中...</div>;
  }

  return (
    <Formik
      initialValues={{ email: '', password: '', age: '' }}
      validationSchema={validationSchema}
      onSubmit={(values) => {
        fetch('/api/register', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(values)
        })
        .then(res => res.json())
        .then(data => {
          data.message ? alert(data.message) : console.error(data.errors);
        });
      }}
    >
      <Form>
        <div>
          <label>邮箱</label>
          <Field type="email" name="email" />
          <ErrorMessage name="email" component="div" style={{ color: 'red' }} />
        </div>
        <div>
          <label>密码</label>
          <Field type="password" name="password" />
          <ErrorMessage name="password" component="div" style={{ color: 'red' }} />
        </div>
        <div>
          <label>年龄</label>
          <Field type="number" name="age" />
          <ErrorMessage name="age" component="div" style={{ color: 'red' }} />
        </div>
        <button type="submit">注册</button>
      </Form>
    </Formik>
  );
}

export default RegisterForm;

注意事项

  • 扩展规则:如果需要正则匹配、枚举值等复杂规则,可在JSON Schema中添加对应字段(如"matches": {"regex": "^[a-z]+$", "message": "仅支持小写字母"}),并在转换函数中处理。
  • 版本控制:若Schema有更新,可在接口路径中加入版本号(如/api/v1/validation-schemas/xxx),避免前后端版本不一致。
  • 安全性:不要在Schema中暴露敏感逻辑(如密码哈希规则),仅保留校验相关内容。
  • 错误信息统一:前后端使用相同的错误提示文案,保证用户体验一致。

内容的提问来源于stack exchange,提问作者Tarek Hammami

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 07:32:18