1. 程式人生 > 其它 >深入理解 ngrx effect 背後的工作機制

深入理解 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<string, Creator>

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<T,A> 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<P, R> 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
                                  |           | 
                                  -------------

更多Jerry的原創文章,盡在:"汪子熙":