Effect Predicate 模块实战:运行时类型守卫与组合式校验指南

发布时间:2026/9/15 15:43:52
Effect Predicate 模块实战:运行时类型守卫与组合式校验指南 Effect Predicate 模块实战运行时类型守卫与组合式校验指南【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code导读本文基于 effect-smol 仓库的 AI 教程文档 10_predicate/index.md 及其配套示例系统讲解 Effect 标准库中Predicate模块的用法什么是运行时类型守卫Predicate / Refinement、为什么团队规范要求永远不要自己手写isRecord、isString之类的辅助函数、以及如何用and/or/not/compose等 API 把零散检查组合成精确的校验逻辑。读完本文你将掌握一套复用、组合、类型安全的运行时校验方案并能在处理unknown数据如JSON.parse、外部 API 响应、用户输入时写出简洁可靠的守卫代码。一、Predicate 模块是什么Predicate模块位于 packages/effect/src/Predicate.ts约 1880 行其模块级注释定义得很清楚Defines runtime checks for values. APredicateAreturnstrueorfalsefor anA. ARefinementA, Bis a predicate that also narrows the TypeScript type when it succeeds.也就是说该模块提供两类核心类型1.1PredicateA只判断、不收窄export interface Predicatein A { (a: A): boolean }源码见 Predicate.ts。一个PredicateA是一个纯函数对给定的A返回true或false自身不抛异常也不会在类型层面收窄输入。它适合用作可复用的布尔判断特别是要传给数组filter、迭代器或与其他谓词组合的场景。1.2RefinementA, B判断且收窄类型export interface Refinementin A, out B extends A { (a: A): a is B }源码见 Predicate.ts。Refinement是带有类型收窄的谓词当它返回true时TypeScript 会把输入从A收窄为B。这是处理unknown值的核心武器——搭配if或filter使用编译器就能在分支内安全访问具体字段。示例来自源码 JSDocimport { Predicate } from effect const isString: Predicate.Refinementunknown, string (u): u is string typeof u string const data: unknown hello if (isString(data)) { data.toUpperCase() // HELLO类型已收窄为 string }二、核心规范不要自己写isRecord/isString原文档用一句近乎强制的规范点明了本模块的定位NEVERwrite your own helper functions likeisRecordorisString, instead use the helpers from thePredicatemodule.翻译过来就是永远不要自己手写isRecord、isString这类辅助函数直接使用Predicate模块提供的守卫。原因很实际正确性有保障Predicate内置守卫经过完整测试覆盖例如isObject要同时排除null和数组这些边界条件极易在手写时遗漏类型安全内置守卫大多以Refinement形式导出能正确收窄类型而随手写的typeof x string未必带类型谓词标注可组合性模块内守卫的类型签名Refinementunknown, T是为组合 API 量身设计的自写函数类型各异难以接入and/or/compose避免重复代码多模块项目里每个人各写一份isObject语义可能不一致维护成本高。需要说明的是原文档举例的isRecord在当前仓库源码中并无同名导出当前版本里非null、非数组的对象检查由 isObject 承担实现为export function isObject(input: unknown): input is { [x: PropertyKey]: unknown } { return typeof input object input ! null !Array.isArray(input) }注意这一行同时完成了三件事排除typeof为object的null、排除数组、并收窄到可索引对象类型。类似的还有isObjectOrArrayL1010和isObjectKeywordL1105。文档以isRecord/isString为例要传达的精神是一致的运行时检查交给标准库不造轮子。三、内置守卫一览把每个值的检查都交给标准库Predicate模块为 JavaScript 常见值提供了丰富的现成守卫category: guards/predicates全部定义于 Predicate.ts。常用列表如下守卫判断依据源码位置isStringtypeof input stringL556isNumbertypeof input numberL589isBooleantypeof input booleanL622isBigInttypeof input bigintL654isSymboltypeof input symbolL686isPropertyKeystring \| number \| symbolL721isFunctiontypeof input functionL753isUndefinedinput undefinedL784isNullinput nullL844isNullishnull \| undefinedL904isNotNullish非null非undefinedL935isNever恒为falseL958isObject非null、非数组的objectL1042hasProperty对象上存在某属性L1140isTagged_tag字段严格等于给定值L1175isErrorinstanceof ErrorL1208isUint8Arrayinstanceof Uint8ArrayL1238isDateinstanceof DateL1267isIterable具有Symbol.iteratorL1297isPromiseinstanceof PromiseL1326isPromiseLike可 then 化对象L1356isRegExpinstanceof RegExpL1385isSet/isMapinstanceof Set/instanceof MapL490 / L522isTruthy!!inputL458isTupleOf(n)数组长度恰好为nL393isTupleOfAtLeast(n)数组长度至少为nL4263.1 面向标签联合的两个关键守卫hasProperty(property)基于isObjectKeyword(self) (property in self)实现L1140-L1147返回Refinementunknown, { [K in P]: unknown }用于确认某个属性存在isTagged(tag)内部调用hasProperty(self, _tag) self[_tag] tagL1175-L1181用于判别带_tag字段的可辨识联合import { Predicate } from effect const isOk Predicate.isTagged(Ok) isOk({ _tag: Ok, value: 1 }) // true isOk({ _tag: Err, error: boom }) // false这类守卫在解析 Effect 的Either、Option、Exit等数据类型时非常常用。四、组合 APIand/or/not/compose原文档明确指出谓词可以通过Predicate.and、Predicate.or、Predicate.not、Predicate.compose进行组合。这四个 API 的实现都集中在 Predicate.ts 的组合子combinators部分且全部支持柯里化curried与管道pipe两种调用方式。4.1and全部满足才为真export const and: { /* ... 多个重载 ... */ } dual( 2, A(self: PredicateA, that: PredicateA): PredicateA (a) self(a) that(a) )源码见 L1632-L1637。要点语义为逻辑与self(a) that(a)在第一个返回false的谓词处短路当两个参数都是Refinement时返回类型会收窄为两者的交集B C测试覆盖见 Predicate.test.tsconst isPositive: Predicate.Predicatenumber (n) n 0 const isLessThan2: Predicate.Predicatenumber (n) n 2 const p pipe(isPositive, Predicate.and(isLessThan2)) p(1) // true p(-1) // false不满足 isPositive p(3) // false不满足 isLessThan24.2or任一满足即为真export const or dual( 2, A(self: PredicateA, that: PredicateA): PredicateA (a) self(a) || that(a) )源码见 L1586-L1591。要点语义为逻辑或self(a) || that(a)在第一个返回true的谓词处短路当两个参数都是Refinement时返回类型收窄为两者的并集B | C测试见 Predicate.test.tsconst p pipe(isPositive, Predicate.or(isNegative)) p(-1) // true p(1) // true p(0) // false4.3not取反export function notA(self: PredicateA): PredicateA { return (a) !self(a) }源码见 L1554-L1556。实现就是简单的布尔翻转测试见 Predicate.test.tsconst isNotString Predicate.not(Predicate.isString) isNotString(1) // true4.4compose串联两个 Refinement逐级收窄export const compose dual( 2, A, B extends A, C extends B(ab: RefinementA, B, bc: RefinementB, C): RefinementA, C (a): a is C ab(a) bc(a) )源码见 L1420-L1429。这是四个 API 中类型能力最强的一个它把RefinementA, B与RefinementB, C或作用于B的普通Predicate串联成RefinementA, C——第一次收窄的输出恰好是第二次检查的输入最终完成从A到C的两级收窄。测试见 Predicate.test.tsconst isString: Predicate.Refinementunknown, string (u): u is string typeof u string const isNonEmptyString: Predicate.Refinementstring, NonEmptyString (s): s is NonEmptyString s.length 0 const refinement pipe(isString, Predicate.compose(isNonEmptyString)) refinement(a) // true refinement() // false refinement(null) // falsecompose与and的区别值得注意and适用于同一输入A上的多条件叠加compose则用于类型逐级收窄的链式流程如先确认是 string再确认非空。二者一横一纵覆盖了谓词组合的两个维度。五、结构化组合Struct/Tuple/mapInput除布尔组合子外Predicate还提供把多个谓词提升到结构化数据上的工具这些在原文档没有展开但对实战极其重要。5.1Predicate.Struct按字段名逐字段校验对象源码见 L1508-L1525实现要点遍历Object.keys(fields)对每个键依次应用对应谓词遇到第一个失败即返回false短路只检查指定的键忽略额外的键任一字段谓词是Refinement时整体返回Refinement并对字段做类型收窄。import { Predicate } from effect const userCheck Predicate.Struct({ id: Predicate.isNumber, name: Predicate.isString }) userCheck({ id: 1, name: Ada }) // true测试中的短路验证Predicate.test.ts当第一个字段谓词返回false时第二个谓词根本不会被调用calls 1并且{ a: 1, b: ok, extra: true }这类带额外键的对象同样可以通过。5.2Predicate.Tuple按位置逐元素校验元组源码见 L1459-L1475与Struct对称逐个下标应用谓词、同样短路const tupleCheck Predicate.Tuple([(n: number) n 0, Predicate.isString]) tupleCheck([1, ok]) // true tupleCheck([-1, ok]) // false测试见 Predicate.test.ts。5.3mapInput先映射、再判断源码见 L360-L363mapInput(self, f)返回一个新谓词等价于(b) self(f(b))——先用f把B投影为A再应用原谓词。典型用途是检查字符串长度这类需求const isLongerThan2 Predicate.mapInput((s: string) s.length)((n: number) n 2) isLongerThan2(hello) // true测试见 Predicate.test.ts同时验证了柯里化与管道两种调用方式。六、实战从unknown到安全访问基于官方示例展开教程配套代码 10_predicate/01_basics.ts 展示了最基础的用法——对unknown值逐层守卫/** * title Using the Predicate module */ import { Predicate } from effect const thing: unknown { a: 1 } if (Predicate.isObject(thing)) { if (Predicate.isNumber(thing.a)) { console.log(number, thing.a) } }把它升级为真实场景解析不可信的 JSON 数据并安全读取嵌套字段结合本文前面所有知识点import { Predicate, pipe } from effect // 1. 把 JSON.parse 的结果先认定为 unknown强制显式校验 const raw: unknown JSON.parse({user:{id:1,name:Ada,tags:[ts]}}) // 2. 用组合子描述形状 const hasUser Predicate.hasProperty(user) const userIsObject Predicate.compose(hasUser, Predicate.isObject) const isExpectedUser pipe( userIsObject, Predicate.and(Predicate.Struct({ id: Predicate.isNumber, name: Predicate.isString })) ) // 3. 守卫通过后类型自动收窄可以安全访问 if (isExpectedUser(raw)) { console.log(raw.user.id, raw.user.name) // 类型安全 }同样的模式可以套在Either结果、外部 API 响应、环境变量解析等一切运行时才知道真实形状的数据上。从源码结构看这正是 Effect 生态中众多模块如Schema、Config、http客户端底层校验的基础设施之一教程文档也在 ai-docs/src/index.md 中强调查找 Effect 相关知识时应以本仓库文档与源码为准。七、组合子的完整能力矩阵除了and/or/not/compose四个文档点名的组合子模块还提供更多逻辑组合全部有测试覆盖Predicate.test.tsAPI语义短路行为测试位置and逻辑与收窄为交集首个false即停L136or逻辑或收窄为并集首个true即停L126not逻辑非无L116compose两级 Refinement 串联收窄任一级失败即停L18xor恰好一个为真无L146eqv两个谓词结果一致无L155implies蕴含关系无L164nor/nand或非 / 与非无L173 / L182every/some数组全满足 / 任一满足短路L191 / L207例如xorL1667-L1670实现为self(a) ! that(a)即两个谓词结果不同才为真。every/some则把谓词提升到数组层面直接配合Array.prototype风格的语义使用。八、实践建议与注意事项从unknown出发显式收窄JSON.parse、fetch响应、process.env取值的类型都应先落为unknown再用Predicate守卫收窄避免类型断言掩盖运行时错误优先复用内置守卫牢记原文档的NEVER规范——检查对象用isObject、检查字符串用isString、检查可辨识联合用isTagged不要重复造轮子组合优于嵌套多层if可以重构为Predicate.and/Struct/compose的组合表达式让校验逻辑成为可命名、可复用、可单测的独立值利用类型收窄组合时优先选用带Refinement签名的守卫内置守卫基本都满足这样and得到交集、or得到并集、compose得到逐级收窄编译器会替你把关后续访问注意短路语义and、Struct、Tuple都在首个失败处短路or在首个成功处短路。若你的谓词有副作用不建议务必理解求值顺序关注测试与文档完整的组合子测试在 packages/effect/test/Predicate.test.ts源码内每个 API 都带有可运行的 JSDoc 示例是学习与排查行为的最佳参考。小结Effect 的Predicate模块把运行时类型守卫从散落的typeof检查提升为一套可复用、可组合、类型安全的标准设施内置数十个现成守卫覆盖 JS 常见值与标签联合and/or/not/compose提供布尔级组合Struct/Tuple/mapInput提供结构级提升。遵循原文档不要自写isRecord/isString的规范配合本文的实战模式你可以在任何需要处理unknown数据的代码路径上写出比手写if链更可靠、更易维护的校验逻辑。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考