Joi中custom()与external()方法的区别及适用场景
Joi中custom()与external()的核心区别及适用场景
虽然你用两种方法都实现了字符串trim的效果,但它们在设计意图、执行时机和适用场景上有本质区别:
1. 执行时机与验证流程位置
- custom():属于字段级的内置验证扩展,在当前字段的所有内置规则(比如
string()、required())执行完成后立即触发,是Joi核心验证流程的一部分。它可以直接修改字段值并返回,后续的规则(如果有的话)会基于修改后的值继续验证。 - external():属于整个Schema验证完成后的“外部钩子”,会等所有字段的内置规则、custom规则都执行完毕,整个数据结构验证通过后才运行。它更像是对最终验证结果的二次校验或补充。
2. 核心用途与场景
custom() 适合:
- 字段级的同步转换(比如你的trim示例、格式化手机号)
- 轻量的同步自定义验证(比如校验字符串是否符合特定格式)
示例:
const schema = Joi.object({ phone: Joi.string().custom((value, helpers) => { // 清除非数字字符 const cleanedPhone = value.replace(/\D/g, ''); if (cleanedPhone.length !== 11) { return helpers.error('string.invalidPhone'); } return cleanedPhone; // 返回修改后的值 }).required() });external() 适合:
- 需要异步操作的验证(比如查询数据库检查邮箱是否已注册)
- 依赖整个Schema最终验证结果的校验(比如验证两个字段的组合是否符合业务规则)
示例:
const schema = Joi.object({ email: Joi.string().email().external(async (value, helpers) => { // 异步查询数据库 const userExists = await UserModel.findOne({ email: value }); if (userExists) { return helpers.error('any.emailExists'); } return value; }).required() });
3. 异步支持的设计差异
虽然custom()也能返回Promise实现异步,但Joi的设计初衷是让external()专门处理异步场景——它的API更适配异步逻辑,且在错误处理、流程优先级上更适合这类场景。而custom()更偏向同步的字段级处理,用它做异步会显得不符合设计意图。
简单总结:如果是字段内部的同步转换/轻量验证,用custom();如果是需要异步、依赖全局上下文或整个Schema结果的验证,用external()。
内容的提问来源于stack exchange,提问作者Ashutosh Aswal
相关产品推荐
相关产品推荐

