Effect Schema 的 transform 怎么写自定义转换?

接口返回的金额是字符串 "19.9",内部计算却要数字。如果每个用到它的地方都自己写一遍 Number(x),转换就会散落在整个代码库里。

自定义转换用 Schema.decodeTo 搭配 SchemaTransformation.transform,同时给出 decode 和 encode 两个方向,转换就集中在 Schema 里。

先交代一个前置概念:Effect 的 Schema 是双向的,Type 是内部形态,Encoded 是外部形态,transform 定义两者怎样互相转换。Schema 的基本校验见 Effect Schema 基础,transform 在建模中的位置见 用 Effect Schema 怎么把领域规则写进类型?。下面先看内置转换,再看自己怎么写。

有没有现成的转换可用?

有,例如 Schema.NumberFromString:外部是字符串,内部是数字。

import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.NumberFromString)("42")   // 42
Schema.encodeSync(Schema.NumberFromString)(42)             // "42"

解码把 "42" 变成 42,编码再变回 "42"。

自己的转换怎么写?

用 Schema.decodeTo 搭配 SchemaTransformation.transform,给出解码与编码两个方向:

import { Schema, SchemaTransformation } from "effect"

const Trimmed = Schema.String.pipe(
  Schema.decodeTo(
    Schema.String,
    SchemaTransformation.transform({
      decode: (s: string) => s.trim(),
      encode: (s: string) => s
    })
  )
)

读法是「从 String 解码到 String,解码时去掉首尾空白,编码时原样输出」。同样的思路可以用来把 ISO 字符串变成日期对象,或者把逗号分隔字符串变成数组。

转换应该放在哪里?

放在边界的 Schema 里,而不是业务代码里。边界之外是外部表示,边界之内是领域表示,Schema 是两者之间唯一的翻译处。这样内部代码拿到的永远是已转换好的值,不需要在业务里写 new Date(x) 或 Number(x) 这类零散转换。

转换里能做 IO 吗?

不能。转换要遵守一条纪律:它应该是纯的、可预期的。需要查库校验的规则,不属于 Schema 的转换。

示例代码取自我整理的教程章节,没有标注 effect 版本号,这次改写中我也没有重新运行;API 写法以所用 effect 版本的文档为准。