Skip to content

最佳实践与对比

最佳实践

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)  // ✅ 自然的递归引用
}

特性对比表

特性Zodfarrow-schema
定义方式函数式 APIClass 继承
类型推导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 })

下一步

推荐学习路径

  1. 📖 查看 API 完整参考 - 了解所有 API 细节
  2. 🔧 学习 farrow-http 集成 - 在 HTTP 服务中使用
  3. 🎯 探索 farrow-api 代码生成 - 自动生成客户端代码

实践建议

  • 在小项目中试用 farrow-schema
  • 创建可复用的验证器库
  • 为团队编写最佳实践文档

记住:好的 Schema 设计能让 bug 无处遁形,让代码更加类型安全!🚀

这是一个第三方 Farrow 文档站 | 用 ❤️ 和 TypeScript 构建