Reactjs 有没有办法将react-redux和react-router链接起来?

Reactjs 有没有办法将react-redux和react-router链接起来?,reactjs,react-redux,react-router,Reactjs,React Redux,React Router,我有一个应用程序,它使用url参数来更改页面上显示的数据 目前,我正在使用react路由器和react redux。react路由器将参数传递给路由中的组件: <Route path='/annotations/:sessionId/:eventId' render={route => <AnnotationPage sessionId={route.match.params.sessionId} eventId={route.match.params.even

我有一个应用程序,它使用url参数来更改页面上显示的数据

目前,我正在使用react路由器和react redux。react路由器将参数传递给路由中的组件:

<Route path='/annotations/:sessionId/:eventId' render={route => 
       <AnnotationPage sessionId={route.match.params.sessionId} eventId={route.match.params.eventId} />} />

} />
然后,该组件将这些值传递给my
ActionCreator
,my
ActionCreator调用服务器并根据传入的
sessionId
eventId
更新状态

我看到的问题是:

  • 有人可以调用
    ActionCreator
    并更改
    eventId
    sessionId
    ,而无需更新URL参数
  • 现在存储了两个ID。一个处于状态(由
    ActionCreator
    设置),另一个位于URL中

我希望URL参数在我的应用程序中成为“真实的来源”,但我不确定是否有一种优雅的方法可以做到这一点。有人有什么想法吗?

位置发生变化时,react router redux将发送一个类型设置为
“@@router/location\u CHANGE”
的操作。此字符串导出为
位置\u CHANGE
,因此可以导入:

从'react router redux'导入{LOCATION_CHANGE}
减速器可以处理此操作,并通过位置增强状态:

案例位置\u变更:
返回{…状态,…action.payload};
然后URL参数作为Redux状态的一部分,成为应用程序中的“真实来源”

附言

  • 不分派
    位置更改
    操作
  • 调度
    位置\u是否更改
    操作,以便您可以拦截它并决定要添加到Redux存储的数据段(包含在此操作的
    有效负载中)。但路由器不再维护
  • 调度
    位置是否更改
    操作。您不希望拦截此操作以使用其
    有效负载中包含的数据来扩充Redux存储,因为路由器无论如何都会执行此操作。也就是说,它会自动增加Redux存储。但您可能希望出于其他目的拦截
    位置更改
    操作。可以使用其
    take/takeEvery/takeLatest/takeLeading
    截取
    位置更改
    操作,并使用您选择的一个或多个异步和非异步操作进行响应,例如获取、将其他操作分派到Redux存储等

    • 这是一个基于逻辑的问题


      由于您和您的开发人员控制着代码,所以您所能做的最好的事情就是记录该操作创建者,并确保您仅使用从url获取的参数调用它。如果用户更改URL参数并刷新页面,那么尝试将URL作为真相来源绑定到代码仍然可能导致出现数据eventid和sessionid重复的情况。因此,您应该在服务器上执行检查,以确保eventid和sessionid来自正确的源,并且没有被操纵。或者没有存储两个ID。

      希望为可能遇到此ID的任何人更新此ID。我最终实现了类似于@winwiz1建议的东西,但更多的是
      特定的

      我无法让位置更改触发服务器请求,但通过超时检查过滤器变量何时更改,并根据URL更改过滤器变量,我得到了我想要的

      store.ts

      import { LocationChangeAction } from 'connected-react-router';
      ...
      export type FilterActions = 
          | LocationChangeAction
      ...
      export const placeItemListReducer: Reducer<IPlaceItemListState, FilterActions> = (
          state = initialPlaceListState,
          action,
      ) => {
          switch (action.type) {
              case '@@router/LOCATION_CHANGE': {
                  if (placePageRegex.test(action.payload.location.pathname)) {
                      const toParse: string = action.payload.location.pathname
                          .replace('/order', '')
                          .replace('/menu', '')
                          .replace('/edit', '');
                      const newFsiUrl: string = toParse.substring(toParse.lastIndexOf('/') + 1);
      
                      if (newFsiUrl === state.placefsiUrlFilter && 
                          state.version === currStoreVersion) {
                          return state;
                      }
                      else {
                          return {
                              ...state,
                              version: currStoreVersion,
                              placefsiUrlFilter: newFsiUrl,
                              place: null,
                              placeItems: [],
                              lastFilterChangeEpochMilliseconds: Date.now(),
                              productsPerPage: stringNullEmptyOrUndefined(newFsiUrl) ? state.productsPerPage : 500,
                          };
                      }
                  }
                  else {
                      return state;
                  }
              }
      ...
      
      从'connected react router'导入{LocationChangeAction};
      ...
      导出类型筛选器操作=
      |位置改变作用
      ...
      导出常量placeItemListReducer:Reducer=(
      状态=initialPlaceListState,
      行动,
      ) => {
      开关(动作类型){
      案例'@@router/位置\u更改':{
      if(placePageRegex.test(action.payload.location.pathname)){
      const toParse:string=action.payload.location.pathname
      .替换('/order','')
      .replace(“/菜单“,”)
      .替换('/edit','');
      const newFsiUrl:string=toParse.substring(toParse.lastIndexOf('/')+1);
      if(newFsiUrl==state.placefsiUrlFilter&&
      state.version==currStoreVersion){
      返回状态;
      }
      否则{
      返回{
      ……国家,
      版本:currStoreVersion,
      placefsiUrlFilter:newFsiUrl,
      地点:空,
      地点项目:[],
      LastFilterChangePochmillSeconds:Date.now(),
      productsPerPage:stringNullEmptyOrUndefined(newFsiUrl)?state.productsPerPage:500,
      };
      }
      }
      否则{
      返回状态;
      }
      }
      ...
      
      sync.ts

      export const startSyncIntervalActionCreator: ActionCreator<
          ThunkAction<
              Promise<void>,      // The type of the last action to be dispatched - will always be promise<T> for async actions
              IAppState,          // The type for the data within the last action
              string,             // The type of the parameter for the nested function 
              ISyncSuccessAction  // The type of the last action to be dispatched
          >
      > = () => {
          return async (dispatch: ThunkDispatch<any, any, AnyAction>) => {
              if (timer !== undefined) {
                  return;
              }
              
              timer = setInterval(() => dispatch(syncActionCreator()), 100);
          };
      };
      ...
      export const syncActionCreator: ActionCreator<
          ThunkAction<
              Promise<void>,      // The type of the last action to be dispatched - will always be promise<T> for async actions
              IAppState,          // The type for the data within the last action
              string,             // The type of the parameter for the nested function 
              ISyncSuccessAction  // The type of the last action to be dispatched
          >
      > = () => {
          return async (dispatch: ThunkDispatch<any, any, AnyAction>, getState: () => IAppState) => {
              if (getState().syncState.currentlySyncing) {
                  return;
              }
      
              const millisecondsSinceLastSync: number = (getCurrentTimeEpochMilliseconds() - getState().placeItemListState.lastSyncEpochMilliseconds);
              const hasDataTimeout: boolean = millisecondsSinceLastSync > dataTimeoutPeriodMilliseconds;
              if (getState().placeItemListState.lastSyncEpochMilliseconds <= getState().placeItemListState.lastFilterChangeEpochMilliseconds ||
                      hasDataTimeout) {
                  const startPlaceItemSyncAction: IStartPlaceItemSyncAction = {
                      type: 'StartPlaceItemSync',
                      fromDataTimeout: hasDataTimeout,
                  };
                  dispatch(startPlaceItemSyncAction);
      
                  axios.post(
                      process.env.REACT_APP_API_ROOT_URL + 'PlaceItem/sync-flat',
                      {
                          fsiUrlFilter: getState().placeItemListState.placefsiUrlFilter,
                          searchTerms: getState().searchTermsState.placeItemSearchTerms,
                          tagIds: getState().toggleFilterState.placeItemTags.filter(f => f.selected).map(f => f.id),
                          pageNumber: getState().placeItemListState.pageNum,
                          productsPerPage: getState().placeItemListState.productsPerPage,
                      },
                      {
                          headers: { 
                              'Access-Control-Allow-Origin': '*',
                              'Authorization': `Bearer ${getState().authState.authToken}`,
                          }
                      }
                  ).then((res: AxiosResponse<IPlaceItemSyncFlatResponse>) => {
                      const syncResponse: IPlaceItemSyncFlatResponse = res.data;
                      const syncSuccessAction: IPlaceItemSyncSuccessAction = {
                          type: 'PlaceItemSyncSuccess',
                          syncTimestampEpochMilliseconds: startSyncTime,
                          placeData: res.data.placeData,
                          placeItems: syncResponse.placeItems,
                          extraCategories: syncResponse.extraCategories,
                          extras: syncResponse.extras,
                          currentMenuCategories: syncResponse.menuCategories,
                          tags: syncResponse.tags,
                          totalNumberOfPlaces: syncResponse.totalNumberOfPlaces,
                          restaurantOrders: syncResponse.orderFromLast5Days,
                      };
                      dispatch(syncSuccessAction);
                  });
              }
          }
      };
      
      export const startSyncIntervalActionCreator:ActionCreator<
      猛击<
      Promise,//要调度的最后一个操作的类型-将始终是异步操作的Promise
      IAppState,//上次操作中的数据类型
      字符串,//嵌套函数的参数类型
      ISyncSuccessAction//要调度的最后一个操作的类型
      >
      > = () => {
      返回异步(调度:ThunkDispatch)=>{
      如果(计时器!==未定义){
      返回;
      }
      timer=setInterval(()=>dispatch(syncActionCreator()),100);
      };
      };
      ...
      导出常量syncActionCreator:ActionCreator<
      猛击<
      Promise,//要调度的最后一个操作的类型-将始终是异步操作的Promise
      IAppState,//上次操作中的数据类型
      字符串,//嵌套函数的参数类型
      伊斯yncsu
      
      ...
      interface IProps {
          children: React.ReactNode;
          startSync: () => void,
      }
      
      const SyncWrapper: React.FC<IProps> = ({
          children,
          startSync,
      }) => {
          startSync();
      
          return (
              <>{children}</>
          );
      }
      
      const mapStateToProps = (store: IOfflineAppState) => {
          return {
          };
      };
      
      const mapDispatchToProps = (dispatch: ThunkDispatch<any, any, AnyAction>) => {
          return {
              startSync: () => dispatch(startSyncIntervalActionCreator()),
          };
      };
      
      export default connect(
          mapStateToProps,
          mapDispatchToProps,
      )(SyncWrapper);