我正在使用Redux进行状态管理 如何将商店重置为初始状态?
例如,假设我有两个用户帐户(u1
和u2
)。
想象一下以下事件序列:
用户u1
登录应用并执行某些操作,因此我们会在商店中缓存一些数据。
用户u1
退出。
用户u2
在不刷新浏览器的情况下登录应用程序。
此时,缓存的数据将与u1
相关联,我想将其清理干净。
当第一个用户退出时,如何将Redux存储重置为初始状态?
答案 0 :(得分:793)
这样做的一种方法是在应用程序中编写root reducer。
root reducer通常会将操作委托给combineReducers()
生成的reducer。但是,只要它收到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') {
state = undefined
}
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') {
state = undefined
}
return appReducer(state, action)
}
。
重申一下,全新的代码如下所示:
state
请注意,我不是在这里改变状态,在将其传递给另一个函数之前,我只是reassigning the reference一个名为undefined
的局部变量。改变状态对象将违反Redux原则。
如果您使用的是redux-persist,则可能还需要清理存储空间。 Redux-persist会将您的状态副本保存在存储引擎中,并在刷新时从那里加载状态副本。
首先,您需要导入相应的storage engine然后解析状态,然后再将其设置为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);
};
并清理每个存储状态密钥。
ImportError: cannot import name config
答案 1 :(得分:73)
我想指出Dan Abramov接受的评论是正确的,除非我们在使用react-router-redux软件包以及这种方法时遇到一个奇怪的问题。我们的修复是不将状态设置为undefined
,而是仍然使用当前的路由减少器。因此,如果您使用此软件包,我建议您实施以下解决方案
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
const { routing } = state
state = { routing }
}
return appReducer(state, action)
}
答案 2 :(得分:20)
定义一个动作:
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.dispatch(RESET_ACTION)
只是阐述了这个想法。您很可能会有一个动作创建者。更好的方法是拥有LOGOUT_ACTION
。
发送此LOGOUT_ACTION
后。然后,自定义中间件可以使用Redux-Saga或Redux-Thunk拦截此操作。但是,无论如何,您都可以发送另一个动作'RESET'。这样,商店注销和重置将同步进行,您的商店将为另一个用户登录做好准备。
答案 3 :(得分:11)
const reducer = (state = initialState, { type, payload }) => {
switch (type) {
case RESET_STORE: {
state = initialState
}
break
}
return state
}
您还可以触发由要重置为初始存储的所有或部分缩减器处理的操作。一个动作可以触发重置到整个状态,或只是一个看起来适合你的状态。我相信这是最简单,最可控制的方式。
答案 4 :(得分: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)
}
答案 5 :(得分:5)
只是对最佳答案的简化答案:
const rootReducer = combineReducers({
auth: authReducer,
...formReducers,
routing
});
export default (state, action) => (
action.type === 'USER_LOGOUT'
? rootReducer(undefined, action)
: rootReducer(state, action)
)
答案 6 :(得分:4)
结合Dan,Ryan和Rob的方法,考虑保持router
状态并初始化州树中的其他所有内容,我最终得到了这个:
const rootReducer = (state, action) => appReducer(action.type === LOGOUT ? {
...appReducer({}, {}),
router: state && state.router || {}
} : state, action);
答案 7 :(得分:4)
更新NGRX4
如果要迁移到NGRX 4,您可能已经从migration guide注意到用于组合reducers的rootreducer方法已被ActionReducerMap方法替换。起初,这种新的做事方式可能会使重置状态成为挑战。它实际上是直截了当的,但这样做的方式已经改变了。
此解决方案的灵感来自NGRX4 Github docs.
的meta-redurs API部分首先,假设您正在使用NGRX的新ActionReducerMap选项组合您的Reducer:
//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实现相同效果的一种方法。
答案 8 :(得分:3)
我已经创建了一个组件来为Redux提供重置状态的能力,你只需要使用这个组件来增强你的存储并调度一个特定的action.type来触发重置。实施的想法与@Dan Abramov所说的相同。
答案 9 :(得分:2)
被接受的答案帮助我解决了案件。但是,我遇到了必须清除非整体状态的情况。所以-我是这样做的:
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;
希望这对其他人有帮助:)
答案 10 :(得分:2)
我已经创建了清除状态的动作。因此,当我发送一个注销动作创建者时,我也会调度动作来清除状态。
用户记录操作
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
}
};
我不确定这是否是最佳的?
答案 11 :(得分:2)
使用 Redux工具包和/或打字稿:
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);
};
答案 12 :(得分:2)
如果您使用的是redux-actions,则可以使用handleActions
的HOF(高阶函数)快速解决此问题。
import { handleActions } from 'redux-actions';
export function handleActionsEx(reducer, initialState) {
const enhancedReducer = {
...reducer,
RESET: () => initialState
};
return handleActions(enhancedReducer, initialState);
}
然后使用handleActionsEx
代替原始handleActions
来处理缩减器。
Dan's answer对这个问题提出了很好的建议,但对我来说效果不佳,因为我正在使用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
),但我无法忍受它'因为我喜欢元编程。< / p>
答案 13 :(得分:1)
一个对我有用的快速简便的选择是使用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'
})
希望这会有所帮助
答案 14 :(得分:1)
从安全角度来看,记录用户时最安全的做法是重置所有持久状态(ex cookies,localStorage
,IndexedDB
,Web SQL
等)并执行使用window.location.reload()
对页面进行硬刷新。可能是一个草率的开发人员偶然或故意在window
,DOM等中存储了一些敏感数据。吹掉所有持久状态并刷新浏览器是保证前一个用户没有信息的唯一方法被泄露给下一个用户。
(当然,作为共享计算机上的用户,您应该使用&#34;私密浏览&#34;模式,自己关闭浏览器窗口,使用&#34;清除浏览数据&#34;功能等,但作为一名开发人员,我们不能指望每个人都要那么勤奋。
答案 15 :(得分:1)
以下解决方案适合我。
我向meta redurs添加了重置状态函数。关键是使用
return reducer(undefined, action);
将所有Reducer设置为初始状态。由于商店的结构已被破坏,因此返回undefined
导致错误。
<强> /reducers/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 {}
答案 16 :(得分:1)
只是@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);
};
答案 17 :(得分:0)
您可以通过将此代码添加到操作文件中来使化简器的数据无效,
首先导入所有类型:
import * as types from './types';
将此代码添加到注销操作
for(let key of Object.values(types)) {
dispatch({ type: key, payload: [] });
}
答案 18 :(得分:0)
如果您想重置单个减速器
例如
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())
答案 19 :(得分:0)
对我来说,最有效的方法是设置initialState
而不是state
:
const reducer = createReducer(initialState,
on(proofActions.cleanAdditionalInsuredState, (state, action) => ({
...initialState
})),
答案 20 :(得分:0)
已接受答案中的解决方案不做的一件事是清除参数化选择器的缓存。如果您有这样的选择器:
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 docs。
答案 21 :(得分:0)
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
答案 22 :(得分:0)
我为防止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;
}
};
答案 23 :(得分:0)
只需编辑声明减速器的文件
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;
答案 24 :(得分:0)
我发现接受的答案对我有用,但它触发了ESLint no-param-reassign
错误 - https://eslint.org/docs/rules/no-param-reassign
以下是我如何处理它,确保创建状态的副本(根据我的理解,这是要做的还原事件......):
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)
}
但也许创建一个状态副本只是将它传递给另一个创建副本的reducer函数有点过于复杂?这听起来不太好,但更重要的是:
export default (state, action) => {
return appReducer(action.type === "LOGOUT" ? undefined : state, action)
}
答案 25 :(得分:0)
为了让我将状态重置为其初始状态,我编写了以下代码:
const appReducers = (state, action) =>
combineReducers({ reducer1, reducer2, user })(
action.type === "LOGOUT" ? undefined : state,
action
);
答案 26 :(得分:0)
以下解决方案对我有用。
首先在我们应用程序的 初始化 中,减速器状态为 新鲜 和 新 和默认的 InitialState 。
我们必须添加一项操作,以调用APP初始加载以保持 默认状态 。
退出应用程序时,我们可以简单地 reAssign 默认状态 ,而reducer的工作原理与 新 。
主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
});
}
}
希望这可以解决您的问题!
答案 27 :(得分:0)
在服务器中,我有一个变量是: global.isSsr = true 在每个reducer中,我有一个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
});
答案 28 :(得分:0)
除了Dan Abramov的答案外,我们不应该在state = undefined旁边将action明确设置为action = {type:'@@ INIT'}。使用上述动作类型,每个reduce都将返回初始状态。
答案 29 :(得分:0)
使用typescript时的解决方法,建立在Dan的答案之上(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"})
}
}
答案 30 :(得分:0)
为什么不使用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;
}
注意:确保将操作默认值设置为{}
并且您没问题,因为您在切换语句中检查action.type
时不希望遇到错误
答案 31 :(得分:0)
这种方法非常正确:破坏任何特定的状态&#34; NAME&#34;忽视并留住他人。
[word for word in string2.split() if not 'a' in word]
答案 32 :(得分:-1)
另一种选择是:
store.dispatch({type: '@@redux/INIT'})
'@@redux/INIT'
是redux在您createStore
时自动调度的操作类型,因此假设您的Reducer已经有一个默认值,那么这将被这些人抓住并开始你的状态。它可能被认为是redux的私有实现细节,所以买家要小心......
答案 33 :(得分:-3)
onLogout() {
this.props.history.push('/login'); // send user to login page
window.location.reload(); // refresh the page
}
答案 34 :(得分:-7)
只需让您的注销链接清除会话并刷新页面即可。您的商店不需要其他代码。只要您想完全重置状态,页面刷新就是一种简单且易于重复的处理方式。