深入理解 ngrx effect 背后的工作机制
博客地址:https://indepth.dev/posts/1206/understanding-the-magic-behind-ngrx-effects
an action is a constituent of a reducer, as well as of an effect. NgRx ensures that actions are first handled by the reducers, after which they will eventually be intercepted by the effects.
actions 是 reducer 的组成部分,也是 effect 的组成部分。 NgRx 确保操作首先由 reducer 处理,之后它们最终会被 effect 拦截。
Reducer 处理 action,然后被 effect 解析。
Providing the effects
forRoot 和 forFeature 接收的输入参数是其他 .ts 文件 export 的 class, 而不是具体的 class 实例。根据 class 获得 metadata.
EffectsModule.forRoot 只能被调用一次,因为这个方法还会实例化其他 Ngrx 重要的服务,比如 EffectsRunner 和 EffectSources.
Spartacus 里的例子, 并没有使用 forRoot 方法。
effects 输入参数是一个数组:
这些都是具体的 effect 实现 class:
Once the effects (classes) are registered, in order to set them up, an observable will be created (with the help of EffectSources) and subscribed to (thanks to EffectRunner);
reducer: the shape of application
state entity: where the app information is kept, also where the place actions meet reducers, meaning it's where reducers being invoked, which may cause state changes
State 相当于模型层,而 Store 只是消费者和 State 之间的中间件。
state 是应用程序存储数据的地方。
the Store entity - the middleman between the data consumer(e.g: a smart component) and the model(the State entity)
Store 是数据消费者,比如 Angular Component 和 model(就是 state entity) 之间的中间层。
effects 会被 merge.
all the effects(e.g: those created by createEffect for example) will be merged into one single observable whose emitted values will be actions.
Effects 会被 merge 成一个 Observable,后者 emit 的value 就是 actions.
Store 也是 stream 的 Observer:
effect ---->actions |- 被 store intercept
actions$
AC 的含义是一个类型:extends ActionCreator
V = Action,V 如果不指定,默认类型为 Action:
ScannedActionsSubject: comes from @ngrx/store and it is a Subject(thus, an Observable) that emits whenever actions are dispatched, but only after the state changes have been handled.
So, when an action is dispatched(Store.dispatch()), the State entity will first update the application state depending on that action and the current state with the help of the reducers, then it will push that action into an actions stream, created by ScannedActionsSubject.
Store dispatch 之后,首先状态机迁移,应用程序 state 发生变化,这一系列通过 reducer 驱动。然后把 action push 到 action stream 去。
By setting the Actions' source to ScannedActionsSubject, every time we have something like this.actions$.pipe().subscribe(observer) the observer will be part of ScannedActionsSubject's observers list, meaning that when the subject emits an action(e.g: subject.next(action)), all the registered observers will receive it. This should explain why all the effects will receive the same actions, but, with ofType's help, these can be filtered out - OfType 的过滤效果。
OfType
In order to determine which actions should trigger which effects, the OfType custom operator is used.
维护 action 和 effect 的映射关系。
OfType 内部也是用的 RxJS 的 filter Operator:
看看 Observable.pipe 的实现:
export class Observable<T> implements Subscribable<T> { /* ... */ pipe<A, B>(op1: OperatorFunction<T, A>, op2: OperatorFunction<A, B>): Observable<B>; /* ... */ }
where OperatorFunction specifies the type of a function that receives an observable as a parameter and returns another observable:
OperatorFunction: 接收两个类型参数,T 代表原始 Observable 包裹的类型,A 表示返回的新的 Observable 包含的类型。
最终返回一个新的 Observable,类型为 B.
Store.dispatch(event)
It signals that an event that requires state changes is sent from the UI(e.g a smart component).
Store.dispatch() will push the action(event) into an actions stream(which is different from the one that belongs to the effects):
Action
可以理解成指令,通过 UI / Service / Effects 来 dispatch.
Creator is simply a function takes up a parameter of type P and returns an object of type R.
reducer
Reducers are pure functions that are responsible for state changes.
reducer 的 interface 定义:
export interface ActionReducer<T, V extends Action = Action> { (state: T | undefined, action: V): T; }
上述语法定义了一个 interface,该 interface 描述了一个函数类型,大括号内是函数类型的具体定义,小括号为这个函数的输入接口定义,该函数接收两个输入参数,形参为 state 和 action,类型分别为 T(也可以接受 undefined) 和 V, 小括号后面的冒号,定义了返回参数类型也应该为 T.
很明显,这个函数就是一个状态机,基于当前状态和输入的 action(可以理解成指令,触发状态迁移的指令),返回新的状态。
TypeScript 里通过定义接口来描述函数 signature 的这种方式,已经和 ABAP 里的 interface 很不一样了。
以 address-verification.reducer.ts 里的 reducer 为例:该 reducer 如何被 setup?
在 index.ts 里,通过 import * as 来区分这些同名的 reducer:
通过 getReducers 统一返回:
通过 reducerToken 和 getReducers 提供 provider 信息:
Provider 在 @NgModule 提供的元数据里使用:
Store
并不存储数据,只是一个中间件。
源代码:
export class Store<T> extends Observable<T> implements Observer<Action> { constructor( state$: StateObservable, private actionsObserver: ActionsSubject, private reducerManager: ReducerManager ) { super(); this.source = state$; } /* ... */ }
Store 从外界接受数据,即 state$.
Every time the source (state$) emits, the Store class will send the value to its subscribers.
allows consumer ↔️ state communication ⬆️ | |----------- newState ----------- | | <------------------- | | | | Store.source=$state | || | | | <---- storing data | Store | Action | State | | | --------------------> | || | Store.dispatch() | | ----------- ----------- | ⬆️ Action | | newState | | ⬇️ | ------------- | | | Reducer | <---- state changes | | -------------