NGRX 流程和路由 - 组件被访问次数过多

NGRX flow and routing - Component being acessed too many times

在我的应用程序中实现 NGRX 商店后,我发现我的 HomeComponent 被加载了太多次。

流程如下,从头开始:

1 - 调用页面时,尝试加载仪表板,但 AuthGuard 告诉我用户未登录并加载 LoginComponent。

应用-routing.module.ts

const routes: Routes = [
  {
    path: 'login',
    loadChildren: './landing/landing.module#LandingModule'
  },
  {
    path: '',
    canActivate: [AuthGuard],
    loadChildren: './dashboard/dashboard.module#DashboardModule'
  }
];

2 - 然后,用户选择通过 Facebook 登录。

login.component.ts

signInWithFacebook() {
  this.store.dispatch(new FacebookLogIn());
}

3 - reducer 被调用,调用我的 LoginService,如果身份验证正常,发送到 LogInSuccess 效果。继续,我不会post这部分。

4 - 如果登录成功,我必须加载有关用户的其他信息,因此我调用其他商店,然后导航到我的 DashboardComponent。

@Effect({ dispatch: false })
LogInSuccess: Observable<any> = this.actions.pipe(
  ofType(LoginActionTypes.LOGIN_SUCCESS),
  tap(action => {
    this.zone.run(() => {
      this.store.dispatch(new GetData(action.payload.user.email));
      this.store.dispatch(new GetData2(action.payload.user.uid));
      localStorage.setItem('user', JSON.stringify(action.payload.user));
      this.router.navigate(['/dashboard'])
    });
  })
);

5 - 仪表板一起加载 HomeComponent。

仪表板-routing.module.ts

{
  path: 'dashboard',
  component: DashboardComponent,
  canActivate: [AuthGuard],
  children: [
    {
      path: '',
      component: HomeComponent,
    },
    ...
    ...
  ]
}

6 - 存储调用结果如下:

7 - 这就是问题所在。如果我在 HomeComponent 中执行 console.log,我会看到每个调用的商店都被调用了 1 次,如下所示。

问题是:

为什么?

我应该怎么做才能避免所有这些不必要的负载?

如果我删除上面的调度之一,它只会进入 HomeComponent 3 次,而不是图片中的 5 次,因为它删除了 2 次效果。

--更新--

HomeComponent.ts

isTermSigned = false;
homeInfo = {
  isBeta: false,
  isTermSigned: false,
  displayName: '',
  email: ''
};
homeSubscription: Subscription;

constructor(
  private afs: AngularFirestore,
  private router: Router,
  private store: Store<AppState>
) { }

ngOnInit() {
  this.homeSubscription = combineLatest(
    this.store.pipe(select(selectData)),
    this.store.pipe(select(selectStatusLogin))
  ).subscribe(([data, login]) => {
    console.log(login);
    if (login.user) {
      this.homeInfo = {
        isBeta: data.isBeta,
        isTermSigned: data.isBeta,
        displayName: login.user.displayName,
        email: login.user.email
      };
    }
  });
}

--更新2-- 这是数据存储的重要部分

data.action.ts

export class GetData implements Action {
  readonly type = PlayerActionTypes.GET_BETA_USER;
  constructor(public payload: any) {}
}

export class GetDataSuccess implements Action {
  readonly type = PlayerActionTypes.GET_DATA_SUCCESS;
  constructor(public payload: any) {}
}

data.effect.ts

@Effect()
GetData: Observable<any> = this.actions.pipe(
  ofType(PlayerActionTypes.GET_DATA),
  mergeMap(email =>
    this.dataService
      .getData(email)
      .then(data=> {
        return new GetDataSuccess({
          isBeta: data.email ? true : false,
          isTermSigned: data.acceptTerms ? true : false
        });
      })
      .catch(error => {
        return new GetDataError({
          isBetaUser: false,
          isTermSigned: false
        });
      })
  )
);

@Effect({ dispatch: false })
GetDataSuccess: Observable<any> = this.actions.pipe(
  ofType(PlayerActionTypes.GET_DATA_SUCCESS),
  tap(action => {
    localStorage.setItem('data', JSON.stringify(action.payload));
  })
);

data.reducer.ts

export interface State {
  isBeta: boolean;
  isTermSigned: boolean;
}

export const initialState: State = {
  isBeta: false,
  isTermSigned: false
};

export function reducer(state = initialState, action: All): State {
  switch (action.type) {
    case DataActionTypes.GET_DATA_SUCCESS: {
      return {
        ...state,
        isBeta: action.payload.isBeta,
        isTermSigned: action.payload.isTermSigned
      };
    }
    case DataActionTypes.GET_DATA_ERROR: {
      return {
        ...state,
        isBeta: action.payload.isBeta,
        isTermSigned: action.payload.isTermSigned
      };
    }
    ...
    default: {
      const data = JSON.parse(localStorage.getItem('data'));
      if (data) {
        return {
          ...state,
          isBeta: betaUser.isBeta,
          isTermSigned: betaUser.isTermSigned
        };
      } else {
        return state;
      }
    }
  }
}

data.selector.ts

import { AppState } from '../reducers';

export const selectData = (state: AppState) => state.data;

--更新3--

另一件事可能会有所帮助并且让我心碎,当我注销时,调用了一个,而且只有一个,效果但是我的 HomeComponent,它根本没有重定向到它,被调用了两次:

{isAuthenticated: true, user: {…}, errorMessage: null}
{isAuthenticated: false, user: null, errorMessage: null}

我不确定是否完全理解您的上下文和需求,但我认为您的 HomeComponent 没有多次加载。但是,使用 combineLatest 创建的可观察对象收到多次相同的值。

我可以建议您 2 项可能的改进:

1) 使用选择器组成商店的多个切片

例如,您可以创建一个getHomeInfo 选择器来接收您需要的所有信息,而避免在HomeComponent 中调用combineLatest。它更清晰,文档更完整,也更适合下一点。

2) 使用记忆选择器 createSelector

检查托德座右铭中的 good post

Memoized 选择器将避免无用的计算以及在可观察对象中发出无用的值。 您只会在值更新的情况下收到通知。

例子

为了说明这两点,我在 stackblitz 上创建了一个项目: https://stackblitz.com/edit/angular-ajhyz4

没有createSelector:

export const getValueWithoutCreateSelector = (state) => {
  return state.app.value;
};

createSelector:

export const getValue = createSelector(getAppState, (state) => {
  return state.value;
});

组合选择器:

export const getCustomMessage = createSelector(getValue, getText,
  (value, text) => {
    return `Your value is ${value} and text is '${text}'`;
})