ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Effect 4.0 新特性:使用 `Effectable.Mixin` 让既有类无缝成为 Effect

Effect 4.0 新特性:使用 `Effectable.Mixin` 让既有类无缝成为 Effect Effect 4.0 新特性使用Effectable.Mixin让既有类无缝成为 Effect【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect本文依据仓库 .changeset/pre/effectable-mixin.md 这条 changeset 记录展开结合 packages/effect/src/Effectable.ts 源码与 packages/effect/test/Effectable.test.ts 测试完整讲解Effectable.Mixin的设计动机、使用方式、底层原理与行为边界帮助你理解如何在不修改原有类的前提下把任意既有 TypeScript 类升级为可被Effect.gen直接yield的 Effect 值。一、这条 changeset 讲的是什么.changeset/pre/effectable-mixin.md是一份针对effect包bump 级别为patch的发布变更记录其完整内容只有一句话AddEffectable.Mixinto insert the Effect prototype into an existing class inheritance chain. The returned abstract class requires anasEffectmethod and derives its Effect type from that method through polymorphicthis.它宣告了一件事Effectable模块新增了Mixin导出作用是把Effect 原型插入到既有类的继承链中使得原本与 Effect 无关的类实例也能直接被当成 Effect 值来使用。返回的是一个抽象类子类必须实现asEffect方法Effect 的成功/错误/依赖类型则通过**多态thispolymorphic this**从asEffect的返回类型中自动推导。配套的 .changeset/pre.json 表明当前仓库处于pre预发布模式、tag 为rc即该能力属于 Effect 4.0 发布候选阶段引入的新 API源码中Mixin的 JSDoc 标注since 4.0.0。这意味着下文描述的行为以当前仓库源码为准使用前请确认你所安装的effect版本包含该导出。二、Effectable模块的定位三种自定义 Effect 值的入口Effectable是 Effect 提供的底层辅助模块用于让领域自有的值例如服务键、配置描述对象能够被 Effect 运行时求值并且可以在Effect.gen中被yield。模块入口 packages/effect/src/Effectable.ts 的模块注释给出明确定位Low-level helpers for making custom values behave like Effects. The module exposes a prototype builder, an abstract base class, and a mixin that let domain-specific values, such as service keys or configuration descriptions, be evaluated by Effect and yielded insideEffect.gen.模块目前暴露三个导出适用场景各不相同导出版本适用场景Effectable.Prototypesince 4.0.0不依赖类的低层原语方式提供label与evaluate函数运行时以当前 Fiber 为参数调用evaluateEffectable.ts#L32-L42Effectable.ClassA, E, Rsince 2.0.0抽象基类方式直接继承它并实现抽象方法asEffectEffectable.ts#L70-L72Effectable.Mixin(klass)since 4.0.0组合既有类不修改原类把 Effect 原型插入继承链本文主角三者通过 JSDoc 的see互相引用Effectable.ts#L26-L27、Effectable.ts#L65-L66共同构成一套让自定义值表现为 Effect的完整方案。三、Mixin的 API 签名与设计要点Mixin的实现位于 Effectable.ts#L129-L137核心声明为export const Mixin TBase extends abstract new(...args: ReadonlyArrayany) object( klass: TBase ): TBase typeof MixinBase { abstract class Mixed extends klass { abstract asEffect(): Effect.Effectany, any, any } Object.defineProperties(Mixed.prototype, Object.getOwnPropertyDescriptors(proto)) return Mixed as TBase typeof MixinBase }其设计要点可以从源码逐条读出泛型约束TBase extends abstract new(...args: ReadonlyArrayany) object即传入的既可以是具体类也可以是抽象类——只要它具备可调用的构造签名。返回值类型为TBase typeof MixinBase即原类能力 Mixin 带来的 Effect 能力的组合类型。返回抽象类Mixed被声明为abstract强制要求最终子类实现asEffect。changeset 中the returned abstract class requires anasEffectmethod正是对应abstract asEffect(): Effect.Effectany, any, any这一行。类型经由多态this推导MixinBase中通过条件类型AsEffectReturnSelf从this的asEffect返回类型提取 Effect 的三类类型参数Effectable.ts#L74-L83type AsEffectReturnSelf Self extends { asEffect(): infer A extends Effect.Effectany, any, any } ? A : never declare abstract class MixinBase extends Classany, any, any { constructor(...args: ReadonlyArrayany) override readonly [Effect.TypeId]: AsEffectReturnthis[Effect.TypeId] override [Symbol.iterator](): Effect.EffectIteratorAsEffectReturnthis }也就是说子类asEffect()返回Effect.succeed(...)还是Effect.fail(...)决定整个混合类的Success、Error、Services类型——无需手动标注泛型参数。构造参数与实例成员被保留Mixed extends klass直接继承原类的构造逻辑因此new EffectBox(2)仍会执行Box的构造函数原类的实例字段与方法照常可用。Effect 原型通过Object.defineProperties注入把proto见下文底层原理的全部属性描述符定义到Mixed.prototype上从而把 Effect 原型插进继承链而不是替换或修改原类原型。四、快速上手一个完整的可运行示例源码 JSDoc 自带的示例Effectable.ts#L106-L122可以直接照抄运行import { Effect, Effectable } from effect class Box { constructor(readonly value: number) {} } class EffectBox extends Effectable.Mixin(Box) { asEffect() { return Effect.succeed(this.value) } } const box new EffectBox(2) Effect.isEffect(box) // true await Effect.runPromise(box) // 2三个关键观察点EffectBox既是一个Box拥有value字段、可被任何期望Box的代码使用又是一个 EffectEffect.isEffect返回true可被Effect.runPromise直接执行。asEffect只负责把这个类的实例翻译成一个 Effect运行时真正执行的是这个返回值。不需要改动Box的任何代码——这正是Mixin相比直接继承Effectable.Class的核心优势不侵入既有类型。五、底层原理Effect 原型是如何插入继承链的Mixin依赖模块顶部的三个基础设施Effectable.ts#L44-L55const proto PrototypeClassany, any, any({ label: Effectable, evaluate(_) { return this.asEffect() } }) const Base: newA, E, R() Effect.EffectA, E, R (() { const Base function() {} Base.prototype proto return Base as any })()执行链路可以概括为用Prototype构造一个label为Effectable的 Effect 原型其evaluate回调在求值时调用this.asEffect()——这就是为什么混合类必须实现asEffect。把该原型挂到一个空的Base构造函数上Effectable.Class继承Base于是Class的实例天然带有这套求值逻辑。Mixin(klass)创建abstract class Mixed extends klass再把proto的属性描述符拷贝到Mixed.prototype上。此时实例的原型链是instance → Mixed.prototype含 Effect 原型成员→ klass.prototype → ...。测试用例 inserts the Effect prototype between the subclass and original classEffectable.test.ts#L71-L81用遍历原型链的方式验证了这一结论从实例出发逐级向上最终能到达Box.prototype且途中的某个原型上带有Effect.TypeId。需要特别强调的是两条不变量原类原型不被修改测试 does not modify the original class prototypeEffectable.test.ts#L98-L101断言Box.prototype上没有Effect.TypeIdnew Box(1)依然不是 Effect。效果是通过遮蔽实现的JSDoc 明确列出以下同名成员会被 Effect 原型遮蔽shadowpipe、toString、toJSON、[Symbol.iterator]、[Symbol.for(nodejs.util.inspect.custom)]Effectable.ts#L99-L102。测试 shadows base prototype methods without modifying themEffectable.test.ts#L49-L69验证了这一点混合后的实例toJSON()返回{ _id: Effect, op: Effectable }而独立的PrintableBox实例仍返回自己的{ _id: Box }。六、行为验证来自官方测试的完整证据packages/effect/test/Effectable.test.ts 的Mixin分组覆盖了五个维度可以作为你编写自身用例的对照基准测试场景验证点对应测试求值yield* new EffectBox(1)在Effect.gen中得到1evaluates asEffectL19-L23失败传播asEffect返回Effect.fail(...)时Effect.exit得到Exit.fail(1)propagates failuresL25-L34依赖注入混合类实例可通过Effect.provideService获取服务uses provided servicesL36-L47成员保留instanceof EffectBox与instanceof Box均为真构造器、字段、方法可用preserves the original constructor and instance membersL83-L90Effect 行为Effect.isEffect为真pipe可用makes instances behave as EffectsL92-L96其中 uses provided services 用例特别值得注意它展示混合类可以完整参与 Effect 的服务体系——asEffect里通过Effect.map(Multiplier, ...)读取服务外层再以Effect.provideService(Multiplier, 3)提供实现最终得到6。这说明Mixin不是看起来像 Effect的玩具实现而是真正接入了 Effect 的求值、失败与依赖机制。七、与Class、Prototype的取舍Mixin并非唯一选择源码 JSDoc 提供的选用建议可以归纳为Prototype当你不需要类、只需要一个携带label和evaluate的自定义 Effect 值时使用最轻量。Class当你从零定义一个领域类、且希望它天生就是 Effect 时直接继承Effectable.Class并实现asEffect。Mixin当你已经有一个既有类可能是第三方库类型、历史代码、或不能修改原型的共享类需要让其实例也能被 Effect 运行时求值时使用——这是唯一不触碰原类原型的方案。八、使用注意点小结必须实现asEffect返回的类是抽象类编译期即强制约束其返回值决定混合类的Success/Error/Services类型。注意同名成员遮蔽如果你的基类自定义了pipe、toString、toJSON、[Symbol.iterator]或nodejs.util.inspect.custom这些实现会被 Effect 原型遮蔽原类自身不受影响。支持抽象基类TBase的约束是可构造签名抽象类同样可以作为被包装对象。版本前提Mixin属于 Effect 4.0.0 引入的新 API当前仓库处于rc预发布阶段集成前请确认依赖版本满足要求并以你实际安装版本的类型声明为准。整体来看Effectable.Mixin为既有类型 → Effect 值的桥接提供了一条低侵入路径改动只发生在新增的混合子类上原类保持完全不变这正是它被作为patch级别变更补充进Effectable模块的意义所在。【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表