NgRx - при переходе маршрутизатора выполняется несколько действий - PullRequest
1 голос
/ 14 апреля 2020

Я реализовал распознаватель маршрутов, предназначенный для запроса некоторых данных перед переходом на новый маршрут. Вот фрагмент кода, отвечающий за отправку действия в хранилище:

@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_REQUEST и ROUTER_NAVIGATION отправляются во время того же перехода маршрутизатора, который вызывает Хранить, чтобы излучать дважды, в ответ на каждое отправленное действие соответственно. Однако, если я закомментирую строку, которая отправляет действие, и вместо этого поставлю console.log, примерно так:

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

Маршрутизатор будет по-прежнему запускать действия ROUTER_REQUEST и ROUTER_NAVIGATION, но на этот раз store выдаст только один раз, а тестовое сообщение будет выводиться на консоль только один раз.

Как диспетчеризация действия имеет значение?

Не ROUTER_REQUEST и ROUTER_NAVIGATION действия имеют значение в этой ситуации?

Вот StackBlitz , который вы можете запустить.

1 Ответ

1 голос
/ 16 апреля 2020

Очевидно, здесь нет «виновника».

Это поведение является результатом того, как NgRx использует Rx JS 'сущностей.

Сущность Store является observable , для которого source установлено на state$ (объект State - где хранятся данные):

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

    this.source = state$;
  }
}

Источник

Это означает, что каждый подписчик, подписавшийся на store (например, lazy.resolver - this.store.pipe(...)), фактически будет подписываться на State, , что является BehaviorSubject, это означает, что подписчик будет включен в список подписчиков , поддерживаемый этим субъектом.

Как вы знаете, BehaviorSubject отправляет самое последнее значение nexted новому абонент. На это указывает первый раз, когда сообщение регистрируется.

Причина, по которой сообщение регистрируется дважды, заключается в том, что всякий раз, когда вы отправляете действие , несколько вещей происходит внутри State.
Действие будет intercepted в состоянии, и когда это произойдет, все редукторы будут вызваны с текущим состоянием и текущим действием , в результате чего новое состояние , которое немедленно отправлено его подписчикам :

// 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
});

Источник

Итак, когда store изначально подписан, он отправит свое последнее сохраненное значение, но когда вы отправляете другое действие (например), вы делаете внутри следующего обратного вызова tap, a подписчикам будет отправлено новое состояние, которое должно объяснить, почему вы дважды регистрируете сообщения.

Вы можете удивиться, почему мы не попадаем в бесконечное число l oop. Эти строки предотвращают:

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; вы не получите бесконечную ошибку l oop, потому что asyncScheduler (от которой наследуется queueScheduler) будет гарантировать, что если планировщик равен 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

Источник

И поскольку вы используете first() и take(5), вы убедитесь, что после того, как действие завершит свою работу (например, поместит значение дальше в поток), следующее действие в очереди не будет иметь никакого эффекта, потому что в этом случае first() или take(5) будет достигнуто до того, как какие-либо действия в очереди получат шанс выполнить свою задачу.


Если вы хотите узнать больше о том, как ngrx/store работает внутри, я бы рекомендовал взглянуть на Понимание магии c, стоящей за StoreModule NgRx (@ ngrx / магазин)

...