withDecodingDefaultKey 的默认值有哪些坑?
给字段写 Schema.withDecodingDefaultKey(() => "member"),类型检查直接报错;改成 Effect.succeed(new Date()) 给一个 DateTimeUtcFromString 字段,还是报错。默认值看起来只是个值,规则却比想象的多。
withDecodingDefaultKey 的默认值是一个产出 Encoded 值的 Effect,发生在解码之前,并且每次解码都会重新执行。
Effect 是一份「要做什么」的描述,Schema 的 Encoded 是数据在边界上的形态,这两点合起来就解释了下面所有的坑。Schema 在边界处解码的整体思路见 Effect Schema:在系统边界做解码,一份定义多处使用,它派生出的其它能力见 Effect Schema 写一份定义,能派生出哪些东西?。
默认值能写成函数吗?
不能,它必须是 Effect,不是 thunk:
import { Effect, Schema } from "effect"
const Bad = Schema.Struct({
// @ts-expect-error 需要 Effect,传 () => "member" 编译不过
role: Schema.String.pipe(Schema.withDecodingDefaultKey(() => "member"))
})
void Bad因为是 Effect,默认值可以带副作用和服务依赖。这个错误编译期就能抓到。
默认值写 Type 一侧还是 Encoded 一侧?
写 Encoded 一侧。DateTimeUtcFromString 的 Encoded 是 string,所以默认值写字符串:
const Stamp = Schema.Struct({
at: Schema.DateTimeUtcFromString.pipe(
Schema.withDecodingDefaultKey(Effect.succeed("1970-01-01T00:00:00.000Z"))
)
})
console.log(Schema.decodeUnknownSync(Stamp)({}).at) // DateTime.Utc(1970-01-01T00:00:00.000Z)
const WrongSide = Schema.DateTimeUtcFromString.pipe(
// @ts-expect-error 传 Type(Date)会被拒绝
Schema.withDecodingDefaultKey(Effect.succeed(new Date()))
)
void WrongSide想直接给 Type 一侧的值,换成 withDecodingDefaultTypeKey:
const TypeSide = Schema.Struct({
at: Schema.DateTimeUtcFromString.pipe(
Schema.withDecodingDefaultTypeKey(
Effect.sync(() => Schema.decodeUnknownSync(Schema.DateTimeUtcFromString)("1970-01-01T00:00:00.000Z"))
)
)
})
console.log(Schema.decodeUnknownSync(TypeSide)({}).at)写错一侧同样会在编译期报错。
默认值是只算一次,还是每次解码都重算?
每次解码都会重新执行这个 Effect:
let n = 0
const Counter = Schema.Struct({
k: Schema.Number.pipe(Schema.withDecodingDefaultKey(Effect.sync(() => ++n)))
})
const decode = Schema.decodeUnknownSync(Counter)
console.log(decode({}), decode({}), decode({ k: 9 })) // { k: 1 } { k: 2 } { k: 9 }想要「每次都是新的值」(比如新 id)用 Effect.sync,想要「永远是同一个常量」用 Effect.succeed。这一条类型系统抓不到,只会在运行时表现为行为差异,写默认值时要有意识。
上游显式传了 undefined 会怎样?
Key 版本对应 optionalKey:键可以缺失,但不能是 undefined,显式的 undefined 会被拒绝:
try {
decode({ k: undefined })
} catch (e) {
console.log(String(e)) // SchemaError(Expected number at ["k"])
}上游如果会显式传 undefined,用 withDecodingDefault(先 Schema.optional),它覆盖「缺失或 undefined」两种情况。
编码时默认值会保留吗?
默认保留。要把键省掉,传 encodingStrategy: "omit":
const Omit = Schema.Struct({
k: Schema.Number.pipe(
Schema.withDecodingDefaultKey(Effect.succeed(1), { encodingStrategy: "omit" })
)
})
console.log(Schema.encodeSync(Omit)({ k: 1 }), Schema.encodeSync(Counter)({ k: 1 })) // {} { k: 1 }版本是 effect 4.0.5,以上代码在 TypeScript strict 加exactOptionalPropertyTypes下编译并运行过,输出注释是我实际得到的结果。withDecodingDefault覆盖「缺失或 undefined」这一点我没有单独跑过。