Learn Zod By Examole

WORKING…

Zod 是 Typescript 的运行时校验库,学习其以方便类型检查,对代码的优雅以及减少样板代码是比较重要的,何况其除类型检查外还有一些更强的约束,对业务代码开发有利。

Zod 最佳的学习文档就是其官方文档,这里的笔记主要是参照 官方文档,但聚焦最常用的部分。

Zod 就同其他的一些类型校验库一样,通过值而非类型的形式去定义类型(且提供从这种类型定义中获取编译期类型的手段),从而将类型信息能够留到运行时以用于校验,学习的重点在于如何将 typescript 的类型定义转换成 Zod 的值定义。

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import {z, ZodError} from 'zod'
// 所有 primitive 类型都有直接的值定义,这种值定义称为 schema

// primitive values
z.string();
z.number();
z.bigint();
z.boolean();
z.date();

// empty types
z.undefined();
z.null();
z.void(); // accepts undefined

// catch-all types
// allows any value
z.any();
z.unknown();

// never type
// allows no values
z.never();

const StringType = z.string()
// 使用 z.infer 类型和 typeof 去取出类型
type StringType = z.infer<typeof StringType> // string

// 可以使用 parse 去解析类型
const str : StringType = StringType.parse("hello") // "hello"
const strErr : StringType = StringType.parse(12) // error

// parse 会解析失败会抛出异常,更函数式的方式是使用 safeParse
// safeParse 会返回 {success: true, data: T} | {success: false, error: ZodError}
const maybeRes = StringType.safeParse(12)
if (maybeRes.success) {
const result : StringType = maybeRes.data
} else {
const error : ZodError<string> = maybeRes.error
}

组合类型

仅有字面量是无法用在业务上的,各种业务模型需要简单类型的组合去描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// 数组和 tuple 类型,使用函数调用的形式去传递类型参数
const NumberArr = z.array(z.number()) // number[]
// 另一种方式
const NumberArr1 = z.number().array()
const StringNumberPair = z.tuple([z.string(), z.number()]) // [string, number]

// 然后是 object,union 类型和 sum 类型,以及 record 的 schema
// type User = {name: string, age: number}
const User = z.object({
name: z.string(),
age: z.number()
})

// object 默认只会接受存在于 schema 中的字段
// 比如,对类型 {name: string},如果尝试 parse {name: 'hello', age: 18},只会得到{name: 'hello'}
// 使用 passthrough 去获得所有值,使用 strict 去严格限制 schema,无论多了值还是少了值均抛出异常

// union 类型和 sum 类型通过链式调用的 and,or 去表达,或者使用 z.intersection,z.union
// type UnionType = {} & number
const UnionType = z.object({}).and(z.number())

// record 类型也是通过函数去传递类型参数
const NumberRecord = z.record(z.number(), z.string()) // { [x: number]: string }

// 字面量也可以定义,通过 z.literal
// type SumType = {success: true, code: number} | {success: false, error: string}
const SumType = z.object({
success: z.literal(true),
code: z.number()
}).or(z.object({
success: z.literal(false),
error: z.string()
}))

// 使用 optional 来表达?,使用 nullable 表达 T | null
const ABC = z.object({
username: z.string(),
age: z.string().optional(), // or z.optional(z.string())
clazz: z.string().nullable() // or z.nullable(z.string())
})
type ABC = z.infer<typeof ABC>

// schema 定义可以互相引用
// 这里引用上面定义的 User
const DB = z.object({
name: z.string(),
users: z.array(User)
})
type DB = z.infer<typeof DB>
/*
type DB = {
name: string;
users: {
name: string;
age: number;
}[];
}
*/

// schema 定义可以用 shape 来互相继承
const Father = z.object({
prop1: z.string(),
prop2: z.number()
})
// type Son0 = { prop1: string, prop2: number, prop3: string }
const Son0 = z.object({
...Father.shape,
prop3: z.string()
})

// Zod 也提供了 extend 和 merge 用来进行继承,其中 extends 接受 object 对象(同 z.object 一致),而 merge 接受 object schema
const Son1 = Father.extend({
prop3: z.string()
})
const Son2 = Father.merge(z.object({
prop3: z.string()
}))

业务校验,自定义校验

对于 string,number 等类型,Zod 提供了除类型之外的其它校验。

1
2
3
4
5
6
7
8
9
10
11
12
// 下列代码可以对 json 进行校验
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
type Json = Literal | { [key: string]: Json } | Json[];
const jsonSchema: z.ZodType<Json> = z.lazy(() =>
z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])
)

// TODO 自定义校验,业务类型转换(进行校验时常用的一些转换操作,比如字符串转 number 等)
// string, number, 数组等都提供了细节校验
// refine 函数允许自定义校验
// transform 函数允许类型转换

常用类型操作符,Promise

TODO

函数,泛型

泛型类型接受类型参数变成实际类型,这本来是在编译期完成的,使用 Zod 时,这个操作通过函数定义和调用去完成。

TODO