Effect 里怎么用判别联合让非法状态无法表示?
状态对象 { status: string; error?: string; data?: Data } 既可能「成功却带着 error」,也可能「失败却没有 error」,非法组合有无数种。
把它拆成判别联合:用 Schema.TaggedStruct 给每个变体注入 _tag,再用 Schema.Union 并起来,每个变体只含自己合法的字段。 非法组合在类型里就写不出来了。
这里依赖的是 Effect 的 Schema:它既做运行时校验,又给出 TypeScript 类型,所以联合定义一次,两边同时生效。Schema 基础见 Effect Schema 基础,整体建模思路见 用 Effect Schema 怎么把领域规则写进类型?。下面看写法、与错误的关系,以及什么时候该拆。
判别联合怎么写?
import { Schema } from "effect"
const LoadState = Schema.Union([
Schema.TaggedStruct("Loading", {}),
Schema.TaggedStruct("Success", { data: Schema.String }),
Schema.TaggedStruct("Failure", { message: Schema.String })
])
type LoadState = typeof LoadState.Type
const render = (r: LoadState) => {
switch (r._tag) {
case "Loading": return "..."
case "Success": return r.data // 此处 r 一定有 data
case "Failure": return r.message // 此处 r 一定有 message
}
}Schema.TaggedStruct 为每个变体注入 _tag 字段,Schema.Union 把它们并起来。switch 里 TypeScript 会据 _tag 自动收窄类型,漏掉一种变体时(配合穷尽检查)编译器会提醒你。
它和 TaggedError 是什么关系?
同一个套路。_tag 是判别字段,catchTag 在失败上做的事,switch 在数据上做,错误与数据共用同一套「带标签的联合」代数。Schema.TaggedError 就是把带标签的结构用在错误上:字段由 Schema 描述,所以错误也能编码、解码,适合跨进程传输。错误建模见 Effect 错误即类型,联合的更多用法见 Effect Schema 的判别联合:TaggedUnion。
取值有限时用什么?
枚举式的取值用 Schema.Literals:
const Role = Schema.Literals(["admin", "member", "guest"])
// 解码时只接受这三个字符串之一把本来有限的取值声明成任意 string,等于放弃了穷尽检查。
什么时候该把可选字段拆成联合?
当你发现自己在写「字段 A 只有在字段 B 为某值时才有意义」,就该拆成联合了。可选字段越堆越多,字段的组合爆炸,非法状态随之爆炸,往往就是联合的信号。
示例代码取自我整理的教程章节,没有标注 effect 版本号,这次改写中我也没有重新运行;API 写法以所用 effect 版本的文档为准。