xyz*_*xyz 397 javascript store redux redux-store
我正在使用Redux进行状态管理.
如何将商店重置为初始状态?
例如,假设我有两个用户帐户(u1
和u2
).
想象一下以下一系列事件:
用户u1
登录应用程序并执行某些操作,因此我们会在商店中缓存一些数据.
用户u1
注销.
用户u2
无需刷新浏览器即可登录应用程序.
此时,缓存的数据将与之关联u1
,我想将其清理干净.
当第一个用户注销时,如何将Redux存储重置为其初始状态?
Dan*_*mov 929
一种方法是在应用程序中编写根reducer.
根减速器通常会将操作委托给生成的reducer combineReducers()
.但是,只要它收到USER_LOGOUT
动作,它就会重新返回初始状态.
例如,如果您的根减速器看起来像这样:
const rootReducer = combineReducers({
/* your app’s top-level reducers */
})
Run Code Online (Sandbox Code Playgroud)
您可以将其重命名为appReducer
并为其编写新的rootReducer
委托:
const appReducer = combineReducers({
/* your app’s top-level reducers */
})
const rootReducer = (state, action) => {
return appReducer(state, action)
}
Run Code Online (Sandbox Code Playgroud)
现在我们只需要教新的操作rootReducer
后返回初始状态USER_LOGOUT
.众所周知,undefined
无论动作如何,当减速器作为第一个参数被调用时,它们应该返回初始状态.让我们使用这个事实来有条件地剥离积累,state
因为我们将它传递给appReducer
:
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
state = undefined
}
return appReducer(state, action)
}
Run Code Online (Sandbox Code Playgroud)
现在,每当USER_LOGOUT
发生火灾时,所有减速器都将重新初始化.如果他们愿意,他们也可以返回与他们最初不同的东西,因为他们也可以检查action.type
.
重申一下,全新代码如下所示:
const appReducer = combineReducers({
/* your app’s top-level reducers */
})
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
state = undefined
}
return appReducer(state, action)
}
Run Code Online (Sandbox Code Playgroud)
请注意,我不是在这里改变状态,我只是在将其传递给另一个函数之前重新分配一个局部变量的引用state
.改变状态对象将违反Redux原则.
如果使用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')
state = undefined;
}
return appReducer(state, action);
};
Run Code Online (Sandbox Code Playgroud)
小智 79
我想指出Dan Abramov接受的评论是正确的,除非我们在使用react-router-redux软件包以及这种方法时遇到了一个奇怪的问题.我们的修复是不设置状态,undefined
而是仍然使用当前的路由减少器.因此,如果您使用此软件包,我建议您实施以下解决方案
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
const { routing } = state
state = { routing }
}
return appReducer(state, action)
}
Run Code Online (Sandbox Code Playgroud)
小智 32
定义一个动作:
const RESET_ACTION = {
type: "RESET"
}
Run Code Online (Sandbox Code Playgroud)
然后在每个减速器中假设您正在使用switch
或if-else
通过每个减速器处理多个动作.我将以此为例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;
}
}
Run Code Online (Sandbox Code Playgroud)
这样,无论何时调用RESET
action,reducer都会使用默认状态更新存储.
现在,对于注销,您可以处理以下内容:
const logoutHandler = () => {
store.dispatch(RESET_ACTION)
// Also the custom logic like for the rest of the logout handler
}
Run Code Online (Sandbox Code Playgroud)
每次用户登录时,都不会刷新浏览器.商店将始终处于默认状态.
store.dispatch(RESET_ACTION)
只是阐述了这个想法.您很可能会有一个动作创建者.一个更好的方法是你有一个LOGOUT_ACTION
.
一旦你发出这个LOGOUT_ACTION
.然后,自定义中间件可以使用Redux-Saga或Redux-Thunk拦截此操作.但是,无论如何,您都可以发送另一个动作'RESET'.这样,商店注销和重置将同步进行,您的商店将准备好进行其他用户登录.
Dan*_*rov 12
const reducer = (state = initialState, { type, payload }) => {
switch (type) {
case RESET_STORE: {
state = initialState
}
break
}
return state
}
Run Code Online (Sandbox Code Playgroud)
您还可以触发由要重置为初始存储的所有或某些缩减器处理的操作.一个动作可以触发重置到整个状态,或只是一个看起来适合你的状态.我相信这是最简单,最可控的方式.
tlr*_*son 12
从安全角度来看,注销用户时最安全的做法是重置所有持久状态(例如 cookie localStorage
、IndexedDB
、Web SQL
、 等)并使用window.location.reload()
. 粗心的开发人员可能会无意或故意将一些敏感数据存储window
在 、 DOM 等中。吹走所有持久状态并刷新浏览器是保证前一个用户的信息不会泄露给下一个用户的唯一方法。
(当然,作为共享电脑的用户,你应该使用“隐私浏览”模式,自己关闭浏览器窗口,使用“清除浏览数据”功能等,但作为开发者我们不能期望每个人总是那个勤奋)
小智 8
使用Redux if已经应用了以下解决方案,假设我已在所有reducers中设置了initialState(例如{user:{name,email}}).在许多组件中,我检查这些嵌套属性,因此使用此修复程序,我阻止我的渲染方法在耦合属性条件上被破坏(例如,如果上面提到的解决方案,则将抛出错误用户的state.user.email未定义).
const appReducer = combineReducers({
tabs,
user
})
const initialState = appReducer({}, {})
const rootReducer = (state, action) => {
if (action.type === 'LOG_OUT') {
state = initialState
}
return appReducer(state, action)
}
Run Code Online (Sandbox Code Playgroud)
我在使用打字稿时的解决方法,建立在Dan Abramov的答案之上(redux 类型使得无法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"})
}
}
Run Code Online (Sandbox Code Playgroud)
只是最佳答案的简化答案:
const rootReducer = combineReducers({
auth: authReducer,
...formReducers,
routing
});
export default (state, action) => (
action.type === 'USER_LOGOUT'
? rootReducer(undefined, action)
: rootReducer(state, action)
)
Run Code Online (Sandbox Code Playgroud)
小智 7
使用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);
};
Run Code Online (Sandbox Code Playgroud)
小智 6
只需清除您的注销链接并刷新页面即可。您的商店不需要额外的代码。每当您想要完全重置状态时,页面刷新都是一种简单且易于重复的处理方法。
更新NGRX4
如果要迁移到NGRX 4,则可能会从迁移指南中注意到,用于合并化简器的rootreducer方法已被ActionReducerMap方法替换。首先,这种新的工作方式可能会使重置状态成为一个挑战。实际上,这很简单,但是方法已经改变。
此解决方案的灵感来自NGRX4 Github文档的meta-reducers API部分。
首先,假设您正在使用NGRX的新ActionReducerMap选项像这样组合减速器:
//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
}
Run Code Online (Sandbox Code Playgroud)
现在,假设您要从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 { }
Run Code Online (Sandbox Code Playgroud)
`
这基本上是使用NGRX 4达到相同效果的一种方法。
结合Dan Abramov的回答、Ryan Irilli的回答和Rob Moorman的回答,为了保持router
状态并初始化状态树中的所有其他内容,我最终得到了这个:
const rootReducer = (state, action) => appReducer(action.type === LOGOUT ? {
...appReducer({}, {}),
router: state && state.router || {}
} : state, action);
Run Code Online (Sandbox Code Playgroud)
如果您正在使用redux-actions,这里有一个使用 HOF(高阶函数)的快速解决方法handleActions
。
import { handleActions } from 'redux-actions';
export function handleActionsEx(reducer, initialState) {
const enhancedReducer = {
...reducer,
RESET: () => initialState
};
return handleActions(enhancedReducer, initialState);
}
Run Code Online (Sandbox Code Playgroud)
然后使用handleActionsEx
代替原来的handleActions
来处理减速器。
丹的回答对这个问题给出了一个很好的想法,但它对我来说效果不佳,因为我正在使用redux-persist
.
当与 一起使用时redux-persist
,简单地传递undefined
状态不会触发持久行为,所以我知道我必须手动从存储中删除项目(在我的例子中是 React Native,因此AsyncStorage
)。
await AsyncStorage.removeItem('persist:root');
Run Code Online (Sandbox Code Playgroud)
或者
await persistor.flush(); // or await persistor.purge();
Run Code Online (Sandbox Code Playgroud)
对我也不起作用——他们只是对我大喊大叫。(例如,抱怨“意外的密钥_persist ...”)
然后我突然想到我想要的只是让每个单独的减速器在RESET
遇到操作类型时返回自己的初始状态。这样,坚持就自然而然地处理了。显然,如果没有上述实用函数(handleActionsEx
),我的代码看起来不会很枯燥(尽管它只是一行代码RESET: () => initialState
),但我无法忍受它,因为我喜欢元编程。
归档时间: |
|
查看次数: |
195225 次 |
最近记录: |