如何重置Redux存储的状态?

我正在使用Redux进行状态管理 如何将存储重置为初始状态?< / p > 例如,假设我有两个用户帐户(u1u2) 想象下面的事件序列:

  1. 用户u1登录到应用程序并做了一些事情,所以我们在存储中缓存一些数据。

  2. 用户“u1”退出。

  3. u2用户登录应用程序时没有刷新浏览器。

此时,缓存的数据将与u1相关联,我想清理它。

当第一个用户注销时,如何将Redux存储重置为初始状态?

465590 次浏览

一种方法是在应用程序中编写一个根减速器。

根减速器通常会将处理操作委托给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作为第一个参数调用约简时,无论操作如何,都应该返回初始状态。让我们使用这个事实,有条件地剥离累积的state,当我们传递给appReducer:

 const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
return appReducer(undefined, action)
}


return appReducer(state, action)
}

现在,无论USER_LOGOUT何时触发,所有约简都将重新初始化。它们还可以返回与初始值不同的值,因为它们也可以检查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);
};

只需将注销链接清除会话并刷新页面。您的商店不需要额外的代码。当您想要完全重置状态时,页面刷新是一种简单且易于重复的处理方法。

我已经创建了一个组件来给Redux重置状态的能力,你只需要使用这个组件来增强你的存储和分派一个特定的action.type来触发重置。实现的思想与丹•阿布拉莫夫回答中所说的是一样的。

Github: # EYZ0

丹•阿布拉莫夫回答是正确的,除了我们在使用react-router-redux包时遇到了一个奇怪的问题。

我们的修复方法是不将状态设置为undefined,而是仍然使用当前的路由减速器。因此,如果您正在使用这个包,我建议实现下面的解决方案

const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
const { routing } = state
state = { routing }
}
return appReducer(state, action)
}

这种方法非常正确:销毁任何特定状态“NAME”以忽略并保留其他状态。

const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
state.NAME = undefined
}
return appReducer(state, action)
}

使用Redux,如果应用了以下解决方案,假设我在所有减速器中设置了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)
}

结合丹•阿布拉莫夫回答瑞安Irilli回答罗伯·摩尔人回答,来解释保持router状态和初始化状态树中的所有其他内容,我最终得到了这样的结果:

const rootReducer = (state, action) => appReducer(action.type === LOGOUT ? {
...appReducer({}, {}),
router: state && state.router || {}
} : state, action);

我已经创建了清除状态的操作。因此,当我分派登出动作创建者时,我也分派动作来清除状态。

用户记录动作

export const clearUserRecord = () => ({
type: CLEAR_USER_RECORD
});

注销操作创建器

export const logoutUser = () => {
return dispatch => {
dispatch(requestLogout())
dispatch(receiveLogout())
localStorage.removeItem('auth_token')
dispatch({ type: 'CLEAR_USER_RECORD' })
}
};

减速机

const userRecords = (state = {isFetching: false,
userRecord: [], message: ''}, action) => {
switch (action.type) {
case REQUEST_USER_RECORD:
return { ...state,
isFetching: true}
case RECEIVE_USER_RECORD:
return { ...state,
isFetching: false,
userRecord: action.user_record}
case USER_RECORD_ERROR:
return { ...state,
isFetching: false,
message: action.message}
case CLEAR_USER_RECORD:
return {...state,
isFetching: false,
message: '',
userRecord: []}
default:
return state
}
};

我不确定这是否是最佳的?

 const reducer = (state = initialState, { type, payload }) => {


switch (type) {
case RESET_STORE: {
state = initialState
}
break
}


return state
}

你也可以触发一个动作,由所有或部分还原器处理,你想重置到初始存储。一个动作可以触发你整个状态的重置,或者只是适合你的一部分。我相信这是最简单、最可控的方法。

为什么你不使用return module.exports.default();)

export default (state = {pending: false, error: null}, action = {}) => {
switch (action.type) {
case "RESET_POST":
return module.exports.default();
case "SEND_POST_PENDING":
return {...state, pending: true, error: null};
// ....
}
return state;
}

注意:确保你将动作默认值设置为{},你是ok的,因为你不想在切换语句中检查action.type时遇到错误。

另一种选择是:

store.dispatch({type: '@@redux/INIT'})

'@@redux/INIT'是redux在你createStore时自动分派的动作类型,所以假设你的减速器都已经有默认值,这将被那些捕获并开始你的状态。不过,它可能被认为是redux的私有实现细节,所以买家要小心……

更新NGRX4

如果你正在迁移到NGRX 4,你可能已经从迁移向导中注意到用于组合减速器的rootreducer方法已经被ActionReducerMap方法所取代。起初,这种新的做事方式可能会使重置状态成为一个挑战。它实际上很简单,但这样做的方式已经改变了。

这个解决方案的灵感来自NGRX4 Github文档。的元还原器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
}

现在,假设您想从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达到同样效果的一种方法。

定义一个动作:

const RESET_ACTION = {
type: "RESET"
}

然后在每个减速器中假设您使用switchif-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;
}
}

这样,无论何时调用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)只是阐述了这个想法。你很可能会有一个用于此目的的动作创建者。更好的方法是使用LOGOUT_ACTION

一旦你分派这个LOGOUT_ACTION。然后,一个自定义中间件可以用Redux-Saga或Redux-Thunk拦截这个动作。然而,这两种方法都可以分派另一个动作“RESET”。通过这种方式,存储注销和重置将同步发生,您的存储将为另一个用户登录做好准备。

只是对@dan-abramov答案的扩展,有时我们可能需要保留某些键不被重置。

const retainKeys = ['appConfig'];


const rootReducer = (state, action) => {
if (action.type === 'LOGOUT_USER_SUCCESS' && state) {
state = !isEmpty(retainKeys) ? pick(state, retainKeys) : undefined;
}


return appReducer(state, action);
};

如果您正在使用redux-actions,这里有一个快速的解决方法,使用HOF(高阶函数)用于handleActions

import { handleActions } from 'redux-actions';


export function handleActionsEx(reducer, initialState) {
const enhancedReducer = {
...reducer,
RESET: () => initialState
};
return handleActions(enhancedReducer, initialState);
}

然后使用handleActionsEx代替原来的handleActions来处理约简。

丹的回答给出了关于这个问题的一个很好的想法,但它并不适合我,因为我使用的是redux-persist.
当与redux-persist一起使用时,简单地传递undefined状态不会触发持久化行为,所以我知道我必须手动从存储中删除项目(在我的情况下,React Native,因此是AsyncStorage)

await AsyncStorage.removeItem('persist:root');

await persistor.flush(); // or await persistor.purge();

对我也没用——他们只是对我大喊大叫。(例如,像“意外的键_persist…”这样抱怨)

然后我突然思考,我想要的只是让每个减速机在遇到RESET动作类型时回到自己的初始状态。这样,持久化就被自然地处理了。显然没有上面的实用函数(handleActionsEx),我的代码不会看起来很干(尽管它只是一行,即RESET: () => initialState),但我不能忍受它,因为我喜欢元编程。

下面的解决方案对我很有效。

我添加了重置状态功能到元还原器。关键在于使用

return reducer(undefined, action);

将所有减速器设置为初始状态。相反,返回undefined会导致错误,因为存储的结构已经被破坏。

/还原剂index.ts

export function resetState(reducer: ActionReducer<State>): ActionReducer<State> {
return function (state: State, action: Action): State {


switch (action.type) {
case AuthActionTypes.Logout: {
return reducer(undefined, action);
}
default: {
return reducer(state, action);
}
}
};
}


export const metaReducers: MetaReducer<State>[] = [ resetState ];

app.module.ts

import { StoreModule } from '@ngrx/store';
import { metaReducers, reducers } from './reducers';


@NgModule({
imports: [
StoreModule.forRoot(reducers, { metaReducers })
]
})
export class AppModule {}

从安全角度来看,注销用户时最安全的做法是重置所有持久状态(例如cookie、localStorageIndexedDBWeb SQL等),并使用window.location.reload()硬刷新页面。可能是粗心的开发人员无意中或有意地将一些敏感数据存储在window上,在DOM中等等。清除所有持久状态并刷新浏览器是保证前一个用户的信息不会泄露给下一个用户的唯一方法。

(当然,作为共享计算机上的用户,你应该使用“私人浏览”模式,自己关闭浏览器窗口,使用“清除浏览数据”功能,等等,但作为开发人员,我们不能期望每个人都总是那么勤奋)

我发现丹•阿布拉莫夫回答对我来说工作得很好,但它触发了ESLint no-param-reassign错误- https://eslint.org/docs/rules/no-param-reassign

下面是我如何处理它,确保创建一个状态的副本(这是,在我的理解,Reduxy的事情要做…):

import { combineReducers } from "redux"
import { routerReducer } from "react-router-redux"
import ws from "reducers/ws"
import session from "reducers/session"
import app from "reducers/app"


const appReducer = combineReducers({
"routing": routerReducer,
ws,
session,
app
})


export default (state, action) => {
const stateCopy = action.type === "LOGOUT" ? undefined : { ...state }
return appReducer(stateCopy, action)
}

但是也许创建一个状态的副本,然后把它传递给另一个减速器函数,它会创建一个状态的副本,这有点过于复杂了?这篇文章读起来不太好,但更切题:

export default (state, action) => {
return appReducer(action.type === "LOGOUT" ? undefined : state, action)
}

我在使用typescript时的解决方案,建立在丹•阿布拉莫夫回答之上(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"})
}
}

除了丹•阿布拉莫夫回答之外,我们不应该明确地将动作设置为action = {type: '@@INIT'}state = undefined吗?采用上述动作方式,各减速器均返回初始状态。

在服务器中,我有一个变量:global.isSsr = true 在每个减速器中,我有一个const: initialState 要重置存储中的数据,我对每个Reducer执行以下操作:

appReducer.js为例:

 const initialState = {
auth: {},
theme: {},
sidebar: {},
lsFanpage: {},
lsChatApp: {},
appSelected: {},
};


export default function (state = initialState, action) {
if (typeof isSsr!=="undefined" && isSsr) { //<== using global.isSsr = true
state = {...initialState};//<= important "will reset the data every time there is a request from the client to the server"
}
switch (action.type) {
//...other code case here
default: {
return state;
}
}
}

最后在服务器的路由器上:

router.get('*', (req, res) => {
store.dispatch({type:'reset-all-blabla'});//<= unlike any action.type // i use Math.random()
// code ....render ssr here
});

只是一个简单的回答丹•阿布拉莫夫回答:

const rootReducer = combineReducers({
auth: authReducer,
...formReducers,
routing
});




export default (state, action) =>
rootReducer(action.type === 'USER_LOGOUT' ? undefined : state, action);

首先在应用程序的< >强劲启动< / >强上,减速器状态为< >强新鲜< / >强< >强新< / >强,默认为< >强InitialState < / >强

我们必须添加一个动作,在APP初始加载时调用持久化<强>默认状态< / >强

当注销应用程序时,我们可以简单地<强> < / >强再分配<强>默认状态< / >强和减速器将像< >强新< / >强一样工作。

APP主容器

  componentDidMount() {
this.props.persistReducerState();
}

主APP减速器

const appReducer = combineReducers({
user: userStatusReducer,
analysis: analysisReducer,
incentives: incentivesReducer
});


let defaultState = null;
export default (state, action) => {
switch (action.type) {
case appActions.ON_APP_LOAD:
defaultState = defaultState || state;
break;
case userLoginActions.USER_LOGOUT:
state = defaultState;
return state;
default:
break;
}
return appReducer(state, action);
};

注销时调用重置状态的动作

function* logoutUser(action) {
try {
const response = yield call(UserLoginService.logout);
yield put(LoginActions.logoutSuccess());
} catch (error) {
toast.error(error.message, {
position: toast.POSITION.TOP_RIGHT
});
}
}

丹•阿布拉莫夫回答帮我破了案子。然而,我遇到了一个案例,并不是整个州都需要清理。所以我是这样做的:

const combinedReducer = combineReducers({
// my reducers
});


const rootReducer = (state, action) => {
if (action.type === RESET_REDUX_STATE) {
// clear everything but keep the stuff we want to be preserved ..
delete state.something;
delete state.anotherThing;
}
return combinedReducer(state, action);
}


export default rootReducer;

对我来说,一个快速而简单的选择是使用redux-reset。这是简单的,也有一些高级选项,为较大的应用程序。

在创建存储区中设置

import reduxReset from 'redux-reset'
// ...
const enHanceCreateStore = compose(
applyMiddleware(...),
reduxReset()  // Will use 'RESET' as default action.type to trigger reset
)(createStore)
const store = enHanceCreateStore(reducers)

在注销函数中调度您的“重置”

store.dispatch({
type: 'RESET'
})

为了将状态重置为初始状态,我编写了以下代码:

const appReducers = (state, action) =>
combineReducers({ reducer1, reducer2, user })(
action.type === "LOGOUT" ? undefined : state,
action
);

为了避免Redux引用初始状态的相同变量,我的建议是:

// write the default state as a function
const defaultOptionsState = () => ({
option1: '',
option2: 42,
});


const initialState = {
options: defaultOptionsState() // invoke it in your initial state
};


export default (state = initialState, action) => {


switch (action.type) {


case RESET_OPTIONS:
return {
...state,
options: defaultOptionsState() // invoke the default function to reset this part of the state
};


default:
return state;
}
};

丹•阿布拉莫夫回答没有做的一件事是清除参数化选择器的缓存。如果你有一个这样的选择器:

export const selectCounter1 = (state: State) => state.counter1;
export const selectCounter2 = (state: State) => state.counter2;
export const selectTotal = createSelector(
selectCounter1,
selectCounter2,
(counter1, counter2) => counter1 + counter2
);

然后你必须像这样在登出时释放它们:

selectTotal.release();

否则,最后一次调用选择器的记忆值和最后一个参数的值仍将在内存中。

代码示例来自ngrx文档

onLogout() {
this.props.history.push('/login'); // send user to login page
window.location.reload(); // refresh the page
}

对我来说,效果最好的是设置initialState而不是state:

  const reducer = createReducer(initialState,
on(proofActions.cleanAdditionalInsuredState, (state, action) => ({
...initialState
})),

如果你想重置一个减速机

例如

const initialState = {
isLogged: false
}
//this will be your action
export const resetReducer = () => {
return {
type: "RESET"
}
}


export default (state = initialState, {
type,
payload
}) => {
switch (type) {
//your actions will come her
case "RESET":
return {
...initialState
}
}
}


//and from your frontend
dispatch(resetReducer())

使用回来的工具包和/或打印稿:

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);
};

你可以通过将此代码添加到动作文件中来清空减速器的数据,

首先导入所有类型:

import * as types from './types';

将此代码添加到注销操作

for(let key of Object.values(types)) {
dispatch({ type: key, payload: [] });
}
npm install redux-reset
import reduxReset from 'redux-reset'
...
const enHanceCreateStore = compose(
applyMiddleware(...),
reduxReset()  // Will use 'RESET' as default action.type to trigger reset
)(createStore)
const store = enHanceCreateStore(reducers)


https://github.com/wwayne/redux-reset

只需编辑声明约简的文件

import { combineReducers } from 'redux';


import gets from '../';


const rootReducer = (state, action) => {
let asReset = action.type === 'RESET_STORE';


const reducers = combineReducers({
gets,
});


const transition = {
true() {
return reducers({}, action);
},
false() {
return reducers(state, action);
},
};
return transition[asReset] && transition[asReset]();
};


export default rootReducer;

使用Redux Toolkit的方法:


export const createRootReducer = (history: History) => {
const rootReducerFn = combineReducers({
auth: authReducer,
users: usersReducer,
...allOtherReducers,
router: connectRouter(history),
});


return (state: Parameters<typeof rootReducerFn>[0], action: Parameters<typeof rootReducerFn>[1]) =>
rootReducerFn(action.type === appActions.reset.type ? undefined : state, action);
};