如何重置Redux商店的状态?

xyz*_*xyz 397 javascript store redux redux-store

我正在使用Redux进行状态管理.
如何将商店重置为初始状态?

例如,假设我有两个用户帐户(u1u2).
想象一下以下一系列事件:

  1. 用户u1登录应用程序并执行某些操作,因此我们会在商店中缓存一些数据.

  2. 用户u1注销.

  3. 用户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)

  • 我很好奇Dan,你能在你的减速机上做这样的事吗?用CLEAR_DATA作为动作.`case'CLEAR_DATA':返回initialState` (13认同)
  • 这是一个版本,你可以动态组合reducer以防你使用async redurs:`export const createRootReducer = asyncReducers => {const appReducer = combineReducers({myReducer ... asyncReducers}); return(state,action)=> {if(action.type ==='LOGOUT_USER'){state = undefined; } return appReducer(state,action); } (11认同)
  • @HussienK可以工作,但不适用于每个减速器的状态. (6认同)
  • 这种方法是否完全清除了国家及其所有历史?我从安全的角度思考:如果已经实现了这一点,一旦触发了`USER_LOGOUT`动作,是否有可能从之前获得状态数据?(例如通过devtools) (3认同)
  • 如果初始状态还包括一些外部数据(即来自 localStorage 的数据)怎么办? (2认同)
  • `if(action.type ==='RESET')返回action.stateFromLocalStorage` (2认同)
  • 嘿,这个解决方案对你们有用吗?在我的情况下,它清除了记录器上的状态,但在 UI 上我看到了相同的以前的组件(例如,如果预先填写了表单,并且在注销并再次登录后,我会看到输入字段填充了以前的数据) (2认同)
  • @DanAbramov 谢谢你的这个例子!但是我想知道这种方法是否足够安全以确保注销后用户的数据不可用?我很好奇我们不需要做`location.reload()`来清理网络请求吗? (2认同)
  • 关于此答案的一个注释。如果您使用的是reduxpersist,则删除_persist键会破坏redux-persist函数,因此对于持久状态对象,您想返回{_persist:state._persist},否则将破坏persist函数。您可以通过注销然后再次登录进行测试,如果您单击刷新,您将看到数据没有保存在redux-persist中,并且您已注销,但是如果您在再次登录之前单击刷新,则一切都很好,如_persist密钥将重新生成。 (2认同)

小智 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)

  • 我认为这里的内容是你可能不想在注销时清除整个状态树 - 这种方法在任何子树的根减少器上同样有​​效,因此将这种技术仅应用于根减少器可能更清楚.你要*清除的子树,而不是选择'特殊'的孩子来*不清楚整个树的根减速器,就像这样 (16认同)
  • 请注意,使用react-redux-router,属性是`router`而不是`rounting` (3认同)
  • 我想我现在遇到了你所指的这个问题,(在注销时它会将路由设置为正确的路径,但会加载一个完全不同的组件)我实现了类似于你的东西来修复它,但我认为有些东西不可变的 js 正在把它聚合起来。我最终创建了一个具有 RESET-STATE 操作的父减速器,并且我从该减速器继承以避免完全接触路由 (2认同)
  • @Mrchief取决于您在`combineReducers()`中定义的内容.....如果您有`combineReducers({routing:routingReducer})`,将如答案中所述 (2认同)

小智 32

定义一个动作:

const RESET_ACTION = {
  type: "RESET"
}
Run Code Online (Sandbox Code Playgroud)

然后在每个减速器中假设您正在使用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; 
  }
}
Run Code Online (Sandbox Code Playgroud)

这样,无论何时调用RESETaction,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'.这样,商店注销和重置将同步进行,您的商店将准备好进行其他用户登录.

  • @worc状态实际上不会是未定义的,因为reducers在收到未定义状态时返回initialState (3认同)
  • @worc认为,通过这种方法,每次有人创建新的减速器时,您都必须记住添加重置案例. (3认同)
  • 我觉得这是比在另一个答案中将状态设置为“未定义”更好的方法。当您的应用程序需要一个状态树而您却给它“未定义”时,需要处理的不仅仅是一棵空树,还有更多的错误和麻烦。 (2认同)
  • 这绝对是正确的做法。将状态设置为除初始状态之外的任何状态都只是自找麻烦 (2认同)

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 localStorageIndexedDBWeb SQL、 等)并使用window.location.reload(). 粗心的开发人员可能会无意或故意将一些敏感数据存储window在 、 DOM 等中。吹走所有持久状态并刷新浏览器是保证前一个用户的信息不会泄露给下一个用户的唯一方法。

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

  • 为什么人们对这个投反对票?当你做一个新的 redux state,内容为空时,你基本上仍然在内存中保留以前的 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)
}
Run Code Online (Sandbox Code Playgroud)


Jac*_*cka 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"})
  }
}
Run Code Online (Sandbox Code Playgroud)


Mat*_*tta 7

只是最佳答案的简化答案:

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)

  • 这个解决方案对我来说很有效,只需稍作改动。我需要添加 undefined 作为状态的可选类型,因此它可以与 configureStore "state: ReturnType&lt;typeof appReducer&gt; | undefined" 一起使用 (10认同)

小智 6

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

  • 我真的不明白为什么人们会否决这样的答案。 (11认同)
  • 如果您使用将存储同步到本地存储的中间件怎么办?那么你的方法就根本行不通了... (2认同)

Tyl*_*own 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达到相同效果的一种方法。


And*_*y_D 5

结合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)


elq*_*sta 5

如果您正在使用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),但我无法忍受它,因为我喜欢元编程。