ngRx继承Effects类失效:新增Effect后核心Effect无法触发
为啥会出现这个诡异的问题?
我之前在ngRx 5.x版本里踩过类似的坑——这个版本的Effects注册逻辑严重依赖类自身的装饰器元数据,而你用的继承方式刚好触发了它的一个边缘bug。
具体来说,ngRx在扫描哪些属性是Effect的时候,是读取类被@Effect()装饰器标记的元数据。当你创建子类NotFreeEffects继承CoreEffects,一开始没加新Effect时,它可能还能“捡到”父类的元数据;但当你给子类加了新的@Effect()装饰器后,Angular的元数据收集机制会被干扰,导致父类的那些核心Effect(比如loadLayout$)的元数据没有被正确合并到子类里。结果就是,付费版模块里注册的NotFreeEffects其实只有你新加的那个Effect,父类的CoreEffects全没生效,自然LoadLayoutSuccessAction发不出来,reducer也收不到。
这种继承方式到底支不支持?
直白点说,ngRx 5.x不推荐也不完全支持这种继承式的写法。虽然没加子类Effect时能凑合用,但这属于官方没覆盖到的场景,随时可能因为一点小修改(比如加个新Effect)就崩掉。就算到了后来的ngRx 6+版本,官方文档也依然更推荐用组合而非继承的方式复用Effect逻辑,本质上就是因为继承很容易触发元数据相关的问题。
那该怎么改?给你三个靠谱的方案
方案1:把通用逻辑抽成服务,子类直接复用
把CoreEffects里的通用Effect逻辑封装成一个独立的服务,然后在付费版和免费版的Effects类里直接调用这个服务的方法。这样既避免了继承的坑,又能复用代码:
// 先搞个封装通用逻辑的服务 @Injectable() export class CoreEffectsService { constructor(private actions$: Actions, private layoutService: LayoutService) {} loadLayout$(): Observable<Action> { return this.actions$ .ofType(CoreLayoutActions.LOAD_LAYOUT) .switchMap(() => this.layoutService.loadLayout()) .map(layoutState => new CoreLayoutActions.LoadLayoutSuccessAction(layoutState)); } } // 付费版的Effects @Injectable() export class NotFreeEffects { // 直接复用核心逻辑 loadLayout$ = this.coreEffectsService.loadLayout$(); // 付费专属的Effect该咋写咋写 @Effect() premiumFeature$: Observable<Action> = this.actions$ .ofType(PremiumActions.PREMIUM_FEATURE) .switchMap(() => this.premiumService.doSomething()) .map(() => new PremiumActions.PremiumFeatureSuccessAction()); constructor(private coreEffectsService: CoreEffectsService, private actions$: Actions, private premiumService: PremiumService) {} } // 免费版的Effects @Injectable() export class FreeEffects { loadLayout$ = this.coreEffectsService.loadLayout$(); constructor(private coreEffectsService: CoreEffectsService) {} }
然后模块里还是正常导入对应的Effects就行,这样不管你给付费版加多少新Effect,核心逻辑都不会受影响。
方案2:拆分Effects模块,用模块组合替代继承
把通用的CoreEffects封装成一个独立的模块,然后付费版模块同时导入核心模块和自己的专属Effects,免费版只导入核心模块:
// 核心Effects模块 @NgModule({ imports: [ EffectsModule.forFeature([CoreEffects]) ] }) export class CoreEffectsModule {} // 付费版应用模块 @NgModule({ imports: [ CoreEffectsModule, EffectsModule.forFeature([NotFreeEffects]) ] }) export class PremiumAppModule {} // 免费版应用模块 @NgModule({ imports: [ CoreEffectsModule ] }) export class FreeAppModule {}
这种方式完全符合ngRx的设计思路,用模块组合来实现复用,从根源上避免了继承带来的元数据问题。
方案3:升级ngRx(最彻底,但要考虑成本)
如果项目允许的话,尽量升级到ngRx 6+版本(最好是稳定版)。后续版本对Effects的元数据处理做了优化,虽然官方还是不推荐继承,但至少不会出现加个新Effect就把老Effect搞没的情况。不过要注意,ngRx 6+要求Angular 6+,而你的Angular CLI是1.7.1(对应Angular 5.x),所以升级的话得同步升级Angular版本,这个成本你得掂量掂量。
内容的提问来源于stack exchange,提问作者TimTheEnchanter

