Effect v4 迁移指南:`Effect.gen` 中 `this` 传入方式的变化与实战改造
发布时间:2026/9/15 13:25:06 作者:尧图编辑部 阅读量:1,286

Effect v4 迁移指南Effect.gen中this传入方式的变化与实战改造【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3codeEffect.gen是 Effect 生态中最常用的「生成器式」effect 编排入口。在从 v3 升级到 v4 时向生成器函数体注入实例上下文this的方式发生了破坏性变化v3 允许把self作为第一个位置参数直接传入v4 则要求将其包裹进{ self: this }选项对象。本篇以 .repos/effect-smol 仓库中的迁移文档 generators.md 为骨架结合 v4 源码实现与仓库内真实使用案例讲清该变化的前因后果、两种写法对照以及批量改造时的注意事项帮助你平滑完成迁移。为什么 v4 改变了Effect.gen的self传参Effect.gen在 v3 中的签名允许把实例本身作为第一个位置参数传入形如Effect.gen(this, function*() { ... })这种写法虽然简洁但在参数设计上并不统一Effect.gen的第一个位置参数既可以是一个this值也可以是生成器函数本身运行时需要靠「第一个参数是否是函数」来判断语义类型推导和重载也会因此变得脆弱。v4 将self收敛为显式的选项对象与Effect.fn、Effect.fnUntraced等 API 的{ self: this }风格保持一致让「第一个位置参数承载生成器函数、选项对象承载上下文」成为统一约定。这一设计在 v4 源码中体现得十分清晰见 Effect.ts 中gen的两个重载export const gen: { // 无 self 版本第一个位置参数直接是生成器函数 Eff extends Effectany, any, any, AEff( f: () GeneratorEff, AEff, never ): EffectAEff, /* E */ ..., /* R */ ... // 有 self 版本第一个位置参数必须是 { readonly self: Self } 选项对象 Self, Eff extends Effectany, any, any, AEff( options: { readonly self: Self }, f: (this: Self) GeneratorEff, AEff, never ): EffectAEff, /* E */ ..., /* R */ ... }注意第二个重载中生成器函数被显式标注为(this: Self) ...这意味着函数体内可以直接使用this访问实例成员而类型系统会严格校验this与传入self的一致性。v3 写法回顾在 v3 中把实例传入Effect.gen的写法如下取自迁移文档 generators.mdimport { Effect } from effect class MyService { readonly local 1 compute Effect.gen(this, function*() { return yield* Effect.succeed(this.local 1) }) }这里Effect.gen(this, function*() { ... })中this作为第一个位置参数直接传入生成器函数体内部通过闭包捕获的this即MyService实例访问this.local。升级到 v4 后这种位置参数写法会直接编译失败需要调整。v4 写法使用{ self: this }选项对象v4 中同一功能的等价写法如下import { Effect } from effect class MyService { readonly local 1 compute Effect.gen({ self: this }, function*() { return yield* Effect.succeed(this.local 1) }) }改动要点仅一处把Effect.gen(this, ...)改为Effect.gen({ self: this }, ...)。{ self: this }选项对象中self的类型被推断为当前类实例MyService因此生成器内部的this依然指向同一个实例this.local的访问、类型推断与运行时行为均保持不变。从运行时角度看v4 的实现internal/effect.ts正是通过「参数个数」来区分这两种调用形态export const gen Self, Eff extends Effect.Effectany, any, any, AEff( ...args: | [options: { readonly self: Self }, body: (this: Self) GeneratorEff, AEff, never] | [body: () GeneratorEff, AEff, never] ): Effect.EffectAEff, ... suspend(() fromIteratorUnsafe( args.length 1 ? args[0]() : (args[1].call(args[0].self) as any) ) )当只传一个参数生成器函数时直接以args[0]()调用当传入两个参数选项对象 生成器函数时通过args[1].call(args[0].self)以self作为this调用生成器。这正是Effect.gen({ self: this }, function*() { ... })得以工作的底层机制this被显式绑定函数体内yield*的每一个 effect 依然可以正常解包同时错误类型E与服务需求类型R会从yield*的 effect 中自动聚合到最终返回的Effect类型上。仓库内的真实应用案例这种「在类字段初始化时把this传入生成器」的模式并非孤例在 v4 源码内部也大量使用可以作为迁移后的参照范式。例如Toolkit.ts 中构建 AI 工具包时使用Effect.gen({ self: this }, function*() { ... })Entity.ts 中构建集群实体处理器时使用Effect.gen({ self: this }, function*() { ... })HttpRouter.ts 中构建路由层时使用Effect.gen({ self: this }, function*() { ... })。这些内部代码都体现了同一原则类实例需要把自身的this绑定进生成器时一律通过{ self: this }选项对象显式传递而不是依赖位置参数。迁移与批量改造实操建议机械替换将Effect.gen(this, function*() {...})统一改写为Effect.gen({ self: this }, function*() {...})。这是纯语法层面的调整函数体内部的this.xxx访问不需要改动。只改需要this的场景如果生成器函数体根本没有访问this则保持Effect.gen(function*() {...})单参数形式不变即可无需引入选项对象。借助类型系统自检v4 重载要求f: (this: Self) Generator...因此若改写后this类型不匹配或选项对象缺字段TypeScript 会在编译期直接报错这为批量改造提供了天然的校验手段。关注Effect.fn的一致性v4 中Effect.fn(Name)({ self: this }, function*(...) {...})也采用{ self: this }选项对象绑定this见 Effect.ts 的示例。迁移Effect.gen的同时可以把同类 API 的this绑定写法一并统一降低长期维护成本。相关参考本主题迁移说明migration/generators.md完整 v3→v4 迁移参考migration/v3-to-v4.mdv4Effect.gen类型签名与文档packages/effect/src/Effect.tsv4Effect.gen运行时实现packages/effect/src/internal/effect.ts仓库内{ self: this }使用案例Toolkit.ts、Entity.ts、HttpRouter.ts【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考