NgRx-路由器过渡期间正在分派多个动作

时间:2020-04-14 12:44:26

标签: angular redux rxjs ngrx

我已经实现了路径解析器,该路径解析器旨在在导航到新路径之前请求一些数据。这是负责将操作分配给商店的代码段:

@Injectable()
export class MyResolver implements Resolve<any> {

  constructor(private store: Store<AppState>) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
    return this.store
      .pipe(
        tap(() => {
          this.store.dispatch(loadSomeData());
        }),
        first()
      );
  }
}

点击适当的路线并触发解析器后,从Redux DevTools中可以看到,在进行导航之前,我的动作已被调度了两次。

enter image description here

好吧,我当时的假设是这是因为ROUTER_REQUESTROUTER_NAVIGATION在同一路由器转换期间被调度,这导致存储响应两次调度的操作而发出两次。但是,如果我注释掉分派动作的那一行并放在console.log那里,就像这样:

tap(() => {
    // this.store.dispatch(loadSomeData());
    console.log('Dispatching action');
  })

路由器存储仍将触发ROUTER_REQUESTROUTER_NAVIGATION动作,但是这次存储只发出一次,并且测试消息仅一次输出到控制台。

动作派遣有何不同?

在这种情况下,ROUTER_REQUESTROUTER_NAVIGATION动作不重要吗?

这是您可以运行的StackBlitz

1 个答案:

答案 0 :(得分:1)

显然,这里没有“罪魁祸首”。

此行为是NgRx如何使用RxJS的实体的结果。

Store实体是可观察的,其source设置为state$State实体-存储数据的实体):< / p>

export class Store<T = object> extends Observable<T>
  implements Observer<Action> {
  constructor(
    state$: StateObservable,
    private actionsObserver: ActionsSubject,
    private reducerManager: ReducerManager
  ) {
    super();

    this.source = state$;
  }
}

Source

这意味着每个订阅store的订阅者(例如:lazy.resolver-this.store.pipe(...))实际上将订阅Statewhich is a BehaviorSubject,这意味着订阅者将属于此主题维护的订阅者列表

您知道,BehaviorSubject将最新的下一个值发送给新订户。这是第一次记录消息时指出的。

两次记录该消息的原因是,每当您调度一个动作时,State内就会发生一些事情。
该动作将处于intercepted状态,并且当其发生时,所有化简器都将以当前状态当前动作进行调用,从而导致< strong>新状态,并立即发送给其订户

// Inside `State.ts`

// Actions stream
const actionsOnQueue$: Observable<Action> = actions$.pipe(
  observeOn(queueScheduler)
);

// Making sure everything happens after the reducers have been set up
const withLatestReducer$: Observable<
  [Action, ActionReducer<any, Action>]
> = actionsOnQueue$.pipe(withLatestFrom(reducer$));

const seed: StateActionPair<T> = { state: initialState }; // Default state
const stateAndAction$: Observable<{
  state: any;
  action?: Action;
}> = withLatestReducer$.pipe(
  scan<[Action, ActionReducer<T, Action>], StateActionPair<T>>(
    reduceState, // Calling the reducers, resulting in a new state
    seed
  )
);

this.stateSubscription = stateAndAction$.subscribe(({ state, action }) => {
  this.next(state); // Send the new state to the subscribers(e.g in lazy.resolver)
  scannedActions.next(action); // Send the action so that it can be intercepted by the effects
});

Source

因此,store最初被订阅时,它将发送其最新的存储值,但是当您调度另一个操作(例如)在tap的下一个回调中执行时,将处于新状态发送给订阅者,这应该解释您为何两次记录该消息。

您可能想知道为什么我们不会陷入无限循环。这些行可以防止:

const actionsOnQueue$: Observable<Action> = actions$.pipe(
  observeOn(queueScheduler)
);

observeOn(queueScheduler)在这里非常重要。每当调度动作时(例如this.store.dispatch(loadSomeData());),它都不会立即传递动作(尽管它使用的是queueScheduler),但它会安排动作,做一些工作(在这种情况下,work是传递操作的任务)。

但是当您执行this.store.dispatch(loadSomeData());时,它将再次到达this.store.dispatch(loadSomeData());,并且到达first()之前。

我猜这就是为什么被称为 queue Scheduler;您不会遇到无限循环错误,因为asyncScheduler(继承自queueScheduler)将确保 scheduler active (一个动作正在执行其工作,例如第一个this.store.dispatch(loadSomeData())),新到达的动作将被添加到队列中,并且在活动动作完成其工作时将被考虑在内:

const { actions } = this;

if (this.active) {
  actions.push(action);
  return;
}

let error: any;
this.active = true;

do {
  if (error = action.execute(action.state, action.delay)) {
    break;
  }
} while (action = actions.shift()!); // exhaust the scheduler queue

Source

并且由于您使用的是first()take(5),因此可以确保在某个动作完成工作之后(例如,将值进一步推入流中),确保队列中的下一个动作获胜不会产生任何效果,因为在这种情况下,在队列中的任何操作都有机会执行其任务之前,会先达到first()take(5)


如果您想了解有关ngrx/store内部工作方式的更多信息,建议您查看Understanding the magic behind StoreModule of NgRx (@ngrx/store)