我正在使用Redux来管理状态。
如何将存储库重置为其初始状态?
例如,假设我有两个用户账户(u1
和u2
)。
想象以下事件序列:
用户
u1
登录应用程序,并执行某些操作,因此我们在存储库中缓存了一些数据。用户
u1
注销。用户
u2
登录应用程序而无需刷新浏览器。
此时,缓存的数据将与u1
相关联,我希望清除它。
当第一个用户注销时,如何将Redux存储库重置为其初始状态?
我正在使用Redux来管理状态。
如何将存储库重置为其初始状态?
例如,假设我有两个用户账户(u1
和u2
)。
想象以下事件序列:
用户u1
登录应用程序,并执行某些操作,因此我们在存储库中缓存了一些数据。
用户u1
注销。
用户u2
登录应用程序而无需刷新浏览器。
此时,缓存的数据将与u1
相关联,我希望清除它。
当第一个用户注销时,如何将Redux存储库重置为其初始状态?
有一种方法是在您的应用程序中编写根减速器。
根减速器通常会将操作委托给由combineReducers()
生成的减速器。 但是,每当它收到USER_LOGOUT
操作时,它会再次返回初始状态。
例如,如果您的根减速器看起来像这样:
const rootReducer = combineReducers({
/* your app’s top-level reducers */
})
你可以将它重命名为appReducer
,并编写一个新的rootReducer
来委派它:const appReducer = combineReducers({
/* your app’s top-level reducers */
})
const rootReducer = (state, action) => {
return appReducer(state, action)
}
现在我们只需要教新的rootReducer
在响应USER_LOGOUT
动作时返回初始状态即可。正如我们所知,当使用undefined
作为第一个参数调用reducer时,无论是什么操作,它们都应该返回初始状态。让我们利用这个事实,在将累积的state
传递给appReducer
时有条件地剥离它:
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
return appReducer(undefined, action)
}
return appReducer(state, action)
}
现在,每当USER_LOGOUT
触发时,所有的reducer都将重新初始化。如果需要,它们也可以返回与初始状态不同的内容,因为它们可以检查action.type
。
再次强调,完整的新代码如下:
const appReducer = combineReducers({
/* your app’s top-level reducers */
})
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
return appReducer(undefined, action)
}
return appReducer(state, action)
}
如果您正在使用redux-persist,您可能还需要清理存储。Redux-persist在存储引擎中保留状态副本,在刷新时将从该处加载状态副本。
首先,您需要导入适当的存储引擎,然后在将状态设置为undefined
之前解析状态并清理每个存储状态键。
const rootReducer = (state, action) => {
if (action.type === SIGNOUT_REQUEST) {
// for all keys defined in your persistConfig(s)
storage.removeItem('persist:root')
// storage.removeItem('persist:otherKey')
return appReducer(undefined, action);
}
return appReducer(state, action);
};
case 'CLEAR_DATA': return initialState
。 - HussienKDan Abramov的答案是正确的,但是当我们在使用react-router-redux包时,遇到了一个奇怪的问题。
我们的解决方法是不将状态设置为undefined
,而是仍然使用当前路由缩减器。因此,如果您正在使用此软件包,请实施以下解决方案。
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
const { routing } = state
state = { routing }
}
return appReducer(state, action)
}
router
而不是 rounting
。 - MrchiefcombineReducers()
中定义的内容..... 如果您有 combineReducers({routing: routingReducer})
,那么它将如答案所述。 - Ben Lonsdale定义一个操作:
const RESET_ACTION = {
type: "RESET"
}
假设你在每个reducer中都使用了switch
或if-else
来处理每个reducer中的多个操作。我接下来会以switch
为例。
const INITIAL_STATE = {
loggedIn: true
}
const randomReducer = (state=INITIAL_STATE, action) {
switch(action.type) {
case 'SOME_ACTION_TYPE':
//do something with it
case "RESET":
return INITIAL_STATE; //Always return the initial state
default:
return state;
}
}
这样,每当您调用 RESET
动作时,您的 reducer 将使用默认状态更新存储。
现在,对于注销,您可以按以下方式处理:
const logoutHandler = () => {
store.dispatch(RESET_ACTION)
// Also the custom logic like for the rest of the logout handler
}
每次用户登录时,无需浏览器刷新。Store 将始终保持默认状态。
store.dispatch(RESET_ACTION)
仅是详细说明。您很可能需要一个 action creator 来实现此目的。更好的方法是您拥有一个 LOGOUT_ACTION
。
一旦您调度了这个 LOGOUT_ACTION
。一个自定义中间件可以使用 Redux-Saga 或 Redux-Thunk 拦截该操作。两种方式都可以调度另一个 'RESET' 操作。这样,store 的注销和重置将同步发生,您的 store 将准备好用于另一个用户登录。
undefined
更好。当您的应用程序期望状态树但您提供了undefined
时,与仅仅提供一个空树相比,会产生更多的错误和麻烦。 - worc以下是对Dan Abramov的答案进行简化的回答:
const rootReducer = combineReducers({
auth: authReducer,
...formReducers,
routing
});
export default (state, action) =>
rootReducer(action.type === 'USER_LOGOUT' ? undefined : state, action);
使用 Redux Toolkit 和/或 Typescript:
const appReducer = combineReducers({
/* your app’s top-level reducers */
});
const rootReducer = (
state: ReturnType<typeof appReducer>,
action: AnyAction
) => {
/* if you are using RTK, you can import your action and use it's type property instead of the literal definition of the action */
if (action.type === logout.type) {
return appReducer(undefined, { type: undefined });
}
return appReducer(state, action);
};
从安全的角度考虑,注销用户时最安全的做法是重置所有持久状态(例如cookies、localStorage
、IndexedDB
、Web SQL
等),并使用window.location.reload()
进行硬刷新。有可能因为粗心或者恶意的开发人员在window
、DOM等地方存储了一些敏感信息。清除所有持久状态并刷新浏览器是确保不泄露先前用户信息给下一个用户的唯一方法。
(当然,作为在共用计算机上的用户,您应该使用“隐私浏览”模式、自行关闭浏览器窗口、使用“清除浏览数据”功能等,但是作为开发人员,我们不能期望每个人都始终如此勤奋)
const reducer = (state = initialState, { type, payload }) => {
switch (type) {
case RESET_STORE: {
state = initialState
}
break
}
return state
}
你还可以触发一个操作,由所有或某些 reducer 处理,你希望将其重置为初始 store。一个操作可以触发重置整个状态,或者只是你认为适合的一部分状态。我认为这是最简单和最可控的方式。
如果使用Redux,可以采用以下解决方案,假设已在所有reducer中设置了initialState
(例如{ user: { name, email }}
)。在许多组件中,我会检查这些嵌套属性,因此通过这个修复,我可以避免在耦合属性条件上破坏我的渲染方法(例如,如果state.user.email
,则会抛出一个错误user is undefined
,如果使用上述的解决方案将不会发生这种情况)。
const appReducer = combineReducers({
tabs,
user
})
const initialState = appReducer({}, {})
const rootReducer = (state, action) => {
if (action.type === 'LOG_OUT') {
state = initialState
}
return appReducer(state, action)
}
更新 NGRX4
如果您正在迁移到 NGRX 4,您可能已经从 迁移指南 中注意到,用于组合 reducer 的 rootreducer
方法已被替换为 ActionReducerMap
方法。起初,这种新的做法可能会让重置状态变得困难。实际上,这很简单,但是做事情的方式已经改变了。
这个解决方案受到 NGRX4 Github 文档中的元 reducers API 部分的启发。
首先,假设您正在使用 NGRX 的新选项 ActionReducerMap
来组合您的 reducers:
//index.reducer.ts
export const reducers: ActionReducerMap<State> = {
auth: fromAuth.reducer,
layout: fromLayout.reducer,
users: fromUsers.reducer,
networks: fromNetworks.reducer,
routingDisplay: fromRoutingDisplay.reducer,
routing: fromRouting.reducer,
routes: fromRoutes.reducer,
routesFilter: fromRoutesFilter.reducer,
params: fromParams.reducer
}
app.module
中重置状态。//app.module.ts
import { IndexReducer } from './index.reducer';
import { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store';
...
export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
return function(state, action) {
switch (action.type) {
case fromAuth.LOGOUT:
console.log("logout action");
state = undefined;
}
return reducer(state, action);
}
}
export const metaReducers: MetaReducer<any>[] = [debug];
@NgModule({
imports: [
...
StoreModule.forRoot(reducers, { metaReducers}),
...
]
})
export class AppModule { }
这基本上是使用NGRX 4实现相同效果的一种方法。
在使用 TypeScript 时,我采用了一种方法,基于 Dan Abramov 的答案(由于redux typings使得将undefined作为第一个参数传递给reducer不可能,因此我将初始根状态缓存到常量中):
// store
export const store: Store<IStoreState> = createStore(
rootReducer,
storeEnhacer,
)
export const initialRootState = {
...store.getState(),
}
// root reducer
const appReducer = combineReducers<IStoreState>(reducers)
export const rootReducer = (state: IStoreState, action: IAction<any>) => {
if (action.type === "USER_LOGOUT") {
return appReducer(initialRootState, action)
}
return appReducer(state, action)
}
// auth service
class Auth {
...
logout() {
store.dispatch({type: "USER_LOGOUT"})
}
}