Reactjs 有没有办法将react-redux和react-router链接起来?
我有一个应用程序,它使用url参数来更改页面上显示的数据 目前,我正在使用react路由器和react redux。react路由器将参数传递给路由中的组件: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
<Route path='/annotations/:sessionId/:eventId' render={route =>
<AnnotationPage sessionId={route.match.params.sessionId} eventId={route.match.params.eventId} />} />
} />
然后,该组件将这些值传递给myActionCreator
,myActionCreator调用服务器并根据传入的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);