579

I am using Redux for state management.
How do I reset the store to its initial state?

For example, let’s say I have two user accounts (u1 and u2).
Imagine the following sequence of events:

  1. User u1 logs into the app and does something, so we cache some data in the store.

  2. User u1 logs out.

  3. User u2 logs into the app without refreshing the browser.

At this point, the cached data will be associated with u1, and I would like to clean it up.

How can I reset the Redux store to its initial state when the first user logs out?

4

36 回答 36

1255

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

根减速器通常会将处理操作委托给由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动作。正如我们所知,reducers 应该在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);
};
于 2016-02-26T01:54:44.990 回答
93

Dan Abramov回答是正确的,除了我们在使用 react-router-redux 包和这种方法时遇到了一个奇怪的问题。

我们的解决方法是不将状态设置为,undefined而是仍然使用当前的路由减速器。因此,如果您使用此软件包,我建议您实施以下解决方案

const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    const { routing } = state
    state = { routing } 
  }
  return appReducer(state, action)
}
于 2016-05-20T05:27:01.580 回答
69

定义一个动作:

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.dispatch(RESET_ACTION)只是阐述了这个想法。为此,您很可能有一个动作创建者。一个更好的方法是你有一个LOGOUT_ACTION.

一旦你发送这个LOGOUT_ACTION. 然后,自定义中间件可以使用 Redux-Saga 或 Redux-Thunk 拦截此操作。然而,这两种方式,您都可以发送另一个操作“RESET”。这样,商店注销和重置将同步发生,您的商店将为其他用户登录做好准备。

于 2017-12-27T19:17:12.277 回答
25

只是对Dan Abramov的回答的简化回答

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


export default (state, action) =>
  rootReducer(action.type === 'USER_LOGOUT' ? undefined : state, action);
于 2018-08-13T21:54:39.707 回答
17

使用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);
};
于 2020-09-21T12:45:28.653 回答
15

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

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

于 2018-05-09T23:41:38.927 回答
15
 const reducer = (state = initialState, { type, payload }) => {

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

   return state
 }

您还可以触发由所有或部分减速器处理的操作,您希望将其重置为初始存储。一个动作可以触发你的整个状态的重置,或者只是一个看起来适合你的状态。我相信这是最简单、最可控的方式。

于 2016-11-25T12:24:11.643 回答
12

使用 Redux 如果应用了以下解决方案,假设我initialState在所有减速器中设置了一个(例如{ user: { name, email }})。在许多组件中,我检查了这些嵌套属性,因此通过此修复,我可以防止我的渲染方法在耦合属性条件下被破坏(例如 if ,如果上面提到的解决方案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)
}
于 2016-07-05T09:07:33.970 回答
7

我在使用打字稿时的解决方法,建立在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"})
  }
}
于 2018-06-12T14:09:14.023 回答
7

更新 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
}

现在,假设您想从内部重置状态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 实现相同效果的一种方法。

于 2017-09-07T15:22:31.070 回答
5

结合Dan Abramov回答Ryan Irilli回答Rob Moorman回答,考虑到保持router状态并初始化状态树中的所有其他内容,我最终得到了这个:

const rootReducer = (state, action) => appReducer(action.type === LOGOUT ? {
    ...appReducer({}, {}),
    router: state && state.router || {}
  } : state, action);
于 2016-07-06T15:42:47.390 回答
4

我创建了一个组件来赋予 Redux 重置状态的能力,你只需要使用这个组件来增强你的 store 并调度一个特定action.type的来触发重置。实施的想法与Dan Abramov在他们的回答中所说的相同。

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

于 2016-03-26T17:03:16.857 回答
4

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

于 2016-02-26T15:40:45.177 回答
4

我认为 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;
  }
};
于 2019-04-10T00:06:44.910 回答
4

如果您使用的是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();

对我也不起作用——他们只是对我大喊大叫。(例如,抱怨“Unexpected key _persist ...”

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

于 2018-04-26T11:06:03.597 回答
4

我创建了清除状态的动作。因此,当我分派注销操作创建者时,我也会分派操作以清除状态。

用户记录操作

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

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

于 2016-09-19T20:02:06.580 回答
2

Dan Abramov回答帮助我解决了我的问题。但是,我遇到了不必清除整个状态的情况。所以我这样做了:

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;
于 2018-11-22T18:36:46.733 回答
2

以下解决方案对我有用。

我向元减速器添加了重置状态功能。关键是使用

return reducer(undefined, action);

将所有减速器设置为初始状态。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 {}
于 2018-05-07T10:16:56.810 回答
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);
};
于 2018-03-13T01:56:26.467 回答
0

一个对我有用的快速简单的选择是使用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'
})
于 2019-01-17T09:01:12.863 回答
0

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

const rootReducer = (state, action) => {
    if (action.type === 'USER_LOGOUT') {
        state.NAME = undefined
    }
    return appReducer(state, action)
}
于 2016-05-31T08:25:02.893 回答
0

首先在我们的应用程序启动时,reducer 状态是全新,默认为InitialState

我们必须添加一个调用 APP 初始加载以保持默认状态的操作。

在退出应用程序时,我们可以简单地重新分配默认状态,reducer将像new一样工作。

主应用容器

  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
    });
  }
}
于 2018-08-18T14:25:03.433 回答
0

我发现Dan Abramov回答对我来说效果很好,但它触发了 ESLintno-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)
}

但是也许创建一个状态的副本只是将它传递给另一个创建副本的 reducer 函数有点过于复杂?这读起来不太好,但更中肯:

export default (state, action) => {
    return appReducer(action.type === "LOGOUT" ? undefined : state, action)
}
于 2018-06-04T20:15:20.647 回答
0

Dan Abramov回答没有做的一件事是清除参数化选择器的缓存。如果你有这样的选择器:

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 文档

于 2019-10-31T09:33:50.620 回答
0

使用 Redux 工具包的方法:


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

于 2021-10-17T22:00:15.610 回答
0

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

const appReducers = (state, action) =>
   combineReducers({ reducer1, reducer2, user })(
     action.type === "LOGOUT" ? undefined : state,
     action
);
于 2019-02-13T16:10:30.367 回答
-1

对我来说,最好的方法是设置initialState而不是state

  const reducer = createReducer(initialState,
  on(proofActions.cleanAdditionalInsuredState, (state, action) => ({
    ...initialState
  })),
于 2020-03-19T11:27:25.593 回答
-1

您可以通过将此代码添加到操作文件中来使化简器的数据为空,

首先导入所有类型:

import * as types from './types';

将此代码添加到注销操作

for(let key of Object.values(types)) {
        dispatch({ type: key, payload: [] });
    }
于 2020-11-30T09:12:03.660 回答
-1

如果要重置单个减速器

例如

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())

于 2020-07-18T07:42:02.963 回答
-1

除了Dan Abramov回答之外,我们不应该明确地将行动设置为action = {type: '@@INIT'}并排吗state = undefined?使用上述动作类型,每个减速器都返回初始状态。

于 2018-07-14T09:50:27.310 回答
-1

另一种选择是:

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

'@@redux/INIT'是 redux 在你时自动调度的动作类型createStore,所以假设你的减速器都已经有一个默认值,这会被那些捕捉到并重新开始你的状态。不过,它可能被认为是 redux 的私有实现细节,所以买家要小心......

于 2017-05-30T16:26:36.383 回答
-1

在服务器中,我有一个变量:global.isSsr = true 并且在每个 reducer 中,我有一个constinitialState 要重置 Store 中的数据,我对每个 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
});
于 2018-08-11T06:38:58.980 回答
-1

你为什么不直接使用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.typeswitch 语句时遇到错误。

于 2016-12-02T17:08:30.570 回答
-1
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
于 2021-07-29T08:49:22.577 回答
-2

只需编辑声明减速器的文件

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;
于 2021-07-30T21:21:19.227 回答
-4

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

于 2019-10-31T19:46:28.670 回答