最佳实践与对比
最佳实践
1. 选择合适的定义方式
- ObjectType:通用推荐,支持递归引用
- Struct:联合类型变体、动态构建
typescript
// ✅ 推荐:复杂模型用 ObjectType
class User extends ObjectType {
profile = Profile
posts = List(Post)
}
// ✅ 推荐:联合类型用 Struct
const Result = Union(
Struct({ success: Literal(true), data: User }),
Struct({ success: Literal(false), error: String })
)2. 使用判别联合类型
typescript
// ✅ 好:使用 type 字段作为判别符
const Event = Union(
Struct({ type: Literal('click'), x: Number, y: Number }),
Struct({ type: Literal('keypress'), key: String }),
Struct({ type: Literal('scroll'), delta: Number })
)
// TypeScript 能够自动类型窄化
function handleEvent(event: TypeOf<typeof Event>) {
switch (event.type) {
case 'click':
console.log(event.x, event.y) // ✅ 类型安全
break
}
}3. 组合 Schema 操作
typescript
// 定义完整模型
class FullUser extends ObjectType {
id = String
name = String
email = String
password = String
role = String
createdAt = Date
updatedAt = Date
}
// 派生 Schema
const PublicUser = pickObject(FullUser, ['id', 'name'])
const CreateUserInput = omitObject(FullUser, ['id', 'createdAt', 'updatedAt'])
const UpdateUserInput = partialObject(CreateUserInput)
const AdminUser = FullUser // 管理员可以看到完整信息4. 专用函数获得更好的类型提示
typescript
// ❌ 不推荐:通用函数缺少字段提示
const SafeUser = omit(FullUser, ['password'])
// ✅ 推荐:专用函数有完整的 IDE 提示
const SafeUser = omitObject(FullUser, ['password']) // IDE 会提示所有字段5. 创建可复用的验证器
typescript
// 定义可复用的验证器
const Email = class extends ValidatorType<string> {
validate(input: unknown) {
const result = Validator.validate(String, input)
if (result.kind === 'Err') return result
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(result.value)) {
return this.Err('Invalid email')
}
return this.Ok(result.value)
}
}
const Phone = RegExp(/^1[3-9]\d{9}$/)
const Username = RegExp(/^[a-zA-Z0-9_]{3,16}$/)
// 在多个 Schema 中复用
class User extends ObjectType {
username = Username
email = Email
phone = Phone
}
class ContactForm extends ObjectType {
email = Email
phone = Phone
}6. 错误处理
typescript
import { Result } from 'farrow-schema'
// 使用 Result 类型统一错误处理
function validateAndCreate(data: unknown): Result<User, string> {
const result = Validator.validate(CreateUserInput, data)
// 利用 kind 进行类型收窄
if (result.kind === 'Err') {
return Err(`Validation failed: ${result.value.message}`)
}
// 业务逻辑验证
if (result.value.age < 18) {
return Err('User must be at least 18 years old')
}
return Ok(createUser(result.value))
}
// 统一处理
const result = validateAndCreate(req.body)
if (result.kind === 'Err') {
return res.status(400).json({ error: result.value })
}
res.json(result.value)与其他库对比
vs Zod
基础定义
typescript
// Zod
import { z } from 'zod'
const UserSchema = z.object({
name: z.string(),
age: z.number(),
email: z.string().email()
})
type User = z.infer<typeof UserSchema>
// farrow-schema
import { ObjectType, String, Number } from 'farrow-schema'
class User extends ObjectType {
name = String
age = Number
email = EmailType // 自定义验证器
}
type UserType = TypeOf<typeof User>递归类型
typescript
// Zod - 需要显式定义递归
type Category = {
name: string
subcategories: Category[]
}
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
subcategories: z.array(CategorySchema)
})
)
// farrow-schema - 直接支持递归
class Category extends ObjectType {
name = String
subcategories = List(Category) // ✅ 自然的递归引用
}特性对比表
| 特性 | Zod | farrow-schema |
|---|---|---|
| 定义方式 | 函数式 API | Class 继承 |
| 类型推导 | z.infer<> | TypeOf<> |
| 递归引用 | 需要 z.lazy() | ✅ 原生支持 |
| 可选字段 | .optional() | Optional() |
| 可空字段 | .nullable() | Nullable() |
| 数据转换 | .transform() | 宽松模式自动转换 |
| 默认值 | .default() | ❌ 不支持 |
| 链式验证 | ✅ .min().max() | 需自定义 ValidatorType |
| Schema 操作 | .omit() .pick() .partial() | omitObject() pickObject() partialObject() |
| 错误信息 | 详细的 issues 数组 | 单一错误 + path |
| 自定义验证 | .refine() .superRefine() | 继承 ValidatorType |
| 元数据提取 | ❌ 不支持 | ✅ Formatter.format() |
| 包大小 | ~8kb (minified) | ~12kb (minified) |
| 生态系统 | 🌟 丰富(trpc、react-hook-form 等) | 🔧 farrow 生态 |
| 学习曲线 | 平缓 | 中等 |
优势对比
Zod 的优势:
- ✅ 更成熟的生态系统(与 tRPC、React Hook Form 等集成)
- ✅ 链式 API,更符合函数式编程风格
- ✅ 内置更多便利方法(
.default(),.transform()等) - ✅ 更详细的错误信息
- ✅ 社区支持更好
farrow-schema 的优势:
- ✅ 递归引用更自然(无需
lazy()) - ✅ Class 继承更接近传统 OOP
- ✅ 元数据提取系统(用于代码生成、文档生成)
- ✅ 与 farrow 生态深度集成(farrow-http、farrow-api)
- ✅ 更灵活的验证器扩展(ValidatorType)
使用场景建议
选择 Zod 如果:
- 你需要丰富的第三方集成(tRPC、React Hook Form 等)
- 你更喜欢函数式 API
- 你需要频繁使用数据转换和默认值
- 你的团队更熟悉 Zod
选择 farrow-schema 如果:
- 你使用 farrow 框架(farrow-http、farrow-api)
- 你需要复杂的递归数据结构
- 你需要元数据提取用于代码生成
- 你更喜欢 Class 继承的方式
- 你需要与 TypeScript class 更好的互操作性
常见问题
Q: ObjectType vs Struct 应该用哪个?
A:
- 默认使用 ObjectType(支持递归引用)
- 仅在联合类型变体和动态构建时使用 Struct
Q: Optional vs Nullable 的区别?
A:
Optional(T)→T | undefined,字段可以省略Nullable(T)→T | null,字段必须存在但值可以是 null
typescript
// Optional: 字段可省略
{ name: 'Alice' } // ✅
{ name: 'Alice', bio: undefined } // ✅
// Nullable: 字段必须存在
{ name: 'Alice' } // ❌ content 缺失
{ name: 'Alice', content: null } // ✅Q: 如何处理验证错误?
A: 推荐使用 result.kind 进行类型收窄:
typescript
const result = Validator.validate(User, data)
// 推荐:使用 kind 字段
if (result.kind === 'Err') {
console.log('Error:', result.value.message)
console.log('Path:', result.value.path?.join('.'))
return
}
// result.value 类型安全
console.log(result.value)Q: 可以在浏览器中使用吗?
A: 可以!farrow-schema 是纯 TypeScript 库,支持浏览器和 Node.js
Q: 如何实现自定义验证逻辑?
A: 继承 ValidatorType:
typescript
class PositiveNumber extends ValidatorType<number> {
validate(input: unknown) {
const result = Validator.validate(Number, input)
if (result.kind === 'Err') return result
if (result.value <= 0) {
return this.Err('Must be positive')
}
return this.Ok(result.value)
}
}速查表
基础类型
typescript
String // 字符串
Number // 数字
Int // 整数
Float // 浮点数
Boolean // 布尔值
Date // 日期
ID // 标识符(非空字符串)
Any // 任意类型
Unknown // 未知类型
Json // JSON 类型复合类型
typescript
List(String) // string[]
Optional(String) // string | undefined(字段可省略)
Nullable(String) // string | null(字段必须存在)
Record(String) // { [key: string]: string }
Tuple(String, Number) // [string, number]
Union(Type1, Type2) // Type1 | Type2
Intersect(Type1, Type2) // Type1 & Type2
Literal('value') // 'value'Schema 操作
typescript
pickObject(Schema, ['field1', 'field2']) // 选择字段
omitObject(Schema, ['field1']) // 排除字段
partialObject(Schema) // 所有字段可选
requiredObject(Schema) // 所有字段必填
keyofObject(Schema) // 获取字段键
Intersect(Schema1, Schema2) // 合并 Schema修饰符
typescript
Strict(Number) // 严格模式验证
NonStrict(Number) // 非严格模式验证
ReadOnly(Type) // 只读(浅层)
ReadOnlyDeep(Type) // 只读(深层)验证器
typescript
import { Validator } from 'farrow-schema/validator'
// 验证数据(默认严格模式)
const result = Validator.validate(Schema, data)
// 使用 kind 字段进行类型收窄(推荐)
if (result.kind === 'Ok') {
console.log(result.value) // 验证成功
} else {
console.log(result.value.message) // 错误信息
console.log(result.value.path) // 错误路径
}
// 宽松模式(允许类型转换)
const result = Validator.validate(Schema, data, { strict: false })
// 创建专用验证器(默认严格模式)
import { createSchemaValidator } from 'farrow-schema/validator'
const validate = createSchemaValidator(Schema)
const validateLenient = createSchemaValidator(Schema, { strict: false })下一步
推荐学习路径:
- 📖 查看 API 完整参考 - 了解所有 API 细节
- 🔧 学习 farrow-http 集成 - 在 HTTP 服务中使用
- 🎯 探索 farrow-api 代码生成 - 自动生成客户端代码
实践建议:
- 在小项目中试用 farrow-schema
- 创建可复用的验证器库
- 为团队编写最佳实践文档
记住:好的 Schema 设计能让 bug 无处遁形,让代码更加类型安全!🚀
