首页 文章

如何重置Redux商店的状态?

提问于
浏览
274

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

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

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

  • 用户 u1 注销 .

  • 用户 u2 登录应用程序而不刷新浏览器 .

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

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

24 回答

  • 660

    从安全角度来看,记录用户时最安全的做法是重置所有持久状态(e.x. cookies, localStorageIndexedDBWeb SQL 等)并使用 window.location.reload() 对页面进行硬刷新 . 一个草率的开发人员可能会意外地或有意地将一些敏感数据存储在DOM中的 window 等 . 吹掉所有持久状态并刷新浏览器是保证以前用户的信息不会泄露给下一个用户的唯一方法 .

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

  • 0

    除了Dan Abramov的答案之外,我们不应该明确将行动设置为action = {type:'@@ INIT'}以及state = undefined . 使用上述操作类型,每个reducer返回初始状态 .

  • 1

    我在使用打字稿时的解决方法, Build 在Dan的答案之上(redux typings使得无法将 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"})
      }
    }
    
  • 0

    定义一个动作:

    const RESET_ACTION = {
      type: "RESET"
    }
    

    然后在每个reducers中假设您使用 switchif-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 action,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' . 这样,商店注销和重置将同步进行,您的商店将准备好进行其他用户登录 .

  • 0

    我想指出Dan Abramov接受的评论是正确的,除非我们在使用react-router-redux软件包以及这种方法时遇到了一个奇怪的问题 . 我们的修复是不将状态设置为 undefined ,而是仍然使用当前路由减少器 . 因此,如果您使用此软件包,我建议您实施以下解决方案

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

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

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

    使用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)
    }
    
  • 0

    只是简单回答最佳答案:

    const rootReducer = combineReducers({
        auth: authReducer,
        ...formReducers,
        routing
    });
    
    
    export default (state, action) => (
        action.type === 'USER_LOGOUT'
            ? rootReducer(undefined, action)
            : rootReducer(state, action)
    )
    
  • 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 来处理reducer .

    Dan's answer给出了一个关于这个问题的好主意,但它并没有't work out well for me, because I'使用 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 ),我的代码只赢了't look DRY (although it'只是一个班轮,即 RESET: () => initialState ),但我不能't stand it '因为我喜欢元编程 .

  • 3
    const reducer = (state = initialState, { type, payload }) => {
    
       switch (type) {
          case RESET_STORE: {
            state = initialState
          }
            break
       }
    
       return state
     }
    

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

  • -7

    我已经创建了清除状态的动作 . 因此,当我发送一个注销动作创建者时,我也会调度动作来清除状态 .

    User record action

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

    Logout action creator

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

    Reducer

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

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

  • -1

    更新NGRX4

    如果要迁移到NGRX 4,您可能已经注意到migration guide用于组合reducers的rootreducer方法已被ActionReducerMap方法替换 . 起初,这种新的做事方式可能会使重置状态成为挑战 . 它实际上是直截了当的,但这样做的方式已经改变 .

    此解决方案的灵感来自NGRX4 Github docs.的meta-reducers 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实现相同效果的一种方法 .

  • 4

    接受的答案帮助我解决了我的问题 . 但是,我遇到了必须清除整个州的情况 . 所以 - 我这样做了:

    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;
    

    希望这可以帮助别人:)

  • 0

    我已经创建了一个组件来为Redux提供重置状态的能力,你只需要使用这个组件来增强你的存储并调度一个特定的action.type来触发重置 . 实施的想法与@Dan Abramov所说的相同 .

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

  • 69

    以下解决方案适合我 .

    我添加了重置状态函数到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 {}
    
  • 0

    只是@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);
    };
    
  • 8

    以下解决方案适合我 .

    首先在我们的应用程序的 initiation 上,reducer状态是 freshnew ,默认为 InitialState .

    我们必须添加一个调用APP初始加载的动作来持续 default state .

    在退出应用程序时,我们可以简单 default state default state 和reducer将像 new 一样工作 .

    Main APP Container

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

    Main APP Reducer

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

    On Logout calling action for resetting state

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

    Hope this solves your problem!

  • 1

    另一种选择是:

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

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

  • 9

    你为什么不用 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;
    }
    

    Note: 确保将操作默认值设置为 {} 并且您没问题,因为您在switch语句中检查 action.type 时不希望遇到错误 .

  • 14

    在服务器中,我有一个变量是: global.isSsr = true 并且在每个reducer中,我有一个const是: initialState 要重置Store中的数据,我对每个Reducer执行以下操作: example with 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;
            }
        }
    }
    

    finally on the server's router:

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

    我发现接受的答案对我来说效果很好,但它触发了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)
    }
    

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

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

    只需将您的注销链接清除会话并刷新页面即可 . 您的商店不需要其他代码 . 只要您想完全重置状态,页面刷新就是一种简单且易于重复的处理方式 .

  • 0

    结合Dan,Ryan和Rob的方法来考虑保持 router 状态并初始化状态树中的其他所有内容,我最终得到了这样的结论:

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

    一种方法是在应用程序中编写根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 作为第一个参数调用它们时,reducers应该返回初始状态 . 让我们使用这个事实有条件地剥离累积的 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 . 改变状态对象将违反Redux原则 .

    如果使用redux-persist,您可能还需要清理存储空间 . Redux-persist会将您的状态副本保存在存储引擎中,并在刷新时从那里加载它们 .

    首先,您需要导入相应的storage engine,然后在将其设置为 undefined 之前解析状态并清除每个存储状态键 .

    const rootReducer = (state, action) => {
        if (action.type === SIGNOUT_REQUEST) {
            Object.keys(state).forEach(key => {
                storage.removeItem(`persist:${key}`);
            });
            state = undefined;
        }
        return AppReducers(state, action);
    };
    

相关问题