我正在使用Redux进行状态管理。
如何将存储重置为初始状态?

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


用户u1登录到应用程序并执行了某些操作,因此我们将一些数据缓存在存储中。
用户u1注销。
/>用户u2登录到应用程序时没有刷新浏览器。

此时,缓存的数据将与u1关联,我想对其进行清理。

当第一个用户注销时,如何将Redux存储恢复到初始状态?

评论

最好是注销时清除状态(从安全角度来看)

#1 楼

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

根减速器通常会将处理操作委托给combineReducers()生成的减速器。但是,每当收到USER_LOGOUT操作时,它都会再次返回初始状态。例如,如果您的root reducer看起来像这样:

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的局部变量的引用。更改状态对象将违反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);
};


评论


我很好奇Dan,您能在减速器中做类似的事情吗?以CLEAR_DATA为动作。案例“ CLEAR_DATA”:返回initialState

–HussienK
16 Jul 20'15:29



@HussienK可以工作,但对于每个减速器都不会起作用。

–科里·丹尼尔森(Cory Danielson)
16年8月15日在22:14

这是一个可以在使用异步减速器的情况下动态组合减速器的版本:export const createRootReducer = asyncReducers => {const appReducer = CombineReducers({myReducer ... asyncReducers}); return(state,action)=> {if(action.type ==='LOGOUT_USER'){state = undefined; } return appReducer(state,action); };

–伊沃·萨贝夫(Ivo Sabev)
16年8月31日在15:06



如果(action.type ==='RESET')返回action.stateFromLocalStorage

–丹·阿布拉莫夫(Dan Abramov)
16 Dec 10'在22:46

这种方法是否可以彻底清除该州及其所有历史?我从安全角度考虑:如果已实施此操作,则一旦触发USER_LOGOUT操作,是否有可能早日获取状态数据? (例如,通过devtools)

– AlexKempton
17年4月20日在8:45



#2 楼

我想指出的是,丹·阿布拉莫夫(Dan Abramov)所接受的评论是正确的,除了在将react-router-redux软件包与这种方法一起使用时,我们遇到了一个奇怪的问题。我们的解决方法是不将状态设置为undefined,而是仍然使用当前的路由减速器。因此,如果您使用的是此软件包,我建议实施以下解决方案

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


评论


我认为这里的要点是您可能不想在注销时清除整个状态树-该方法在任何子树的根reducer上同样有效,因此将这种技术仅应用到子树的root reducer上可能更清楚。您想要清除的子树,而不是挑选“特殊”子树而不要在整个树的根reduce上清除,就像这样

– davnicwil
16年9月6日在18:51

我想我遇到了您现在所指的这个问题((在注销时它将把溃败设置为正确的路径,但会加载一个完整的不同组件)我实现了与您类似的解决方案,但是我认为不可变的js正在将其大规模化。我最终创建了一个具有RESET-STATE动作的父级减速器,并且我从该减速器继承来避免完全接触路由。

– Neta Meta
17-10-19在12:32



遇到类似的问题,已解决。谢谢。

–劳埃德·沃特金(Lloyd Watkin)
18年1月15日在10:10

请注意,使用react-redux-router时,该属性为router且不漫游

– r
18年2月15日在21:53

@Mrchief取决于您在CombineReducers()中定义的内容.....如果您有CombineReducers({routing:routingReducer}),将如答案中所述

–本·朗斯代尔
18年7月12日在13:39



#3 楼

定义一个动作:

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操作时,reduce都会以默认状态更新商店。

现在,要注销,您可以处理以下内容:

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”。这样,商店注销和重置将同步进行,您的商店将准备好另一个用户登录。

评论


我觉得这是比将状态设置为undefined更好的方法,就像在其他答案中一样。当您的应用程序期望状态树而您给它提供未定义状态时,除了空树之外,还有更多的错误和麻烦要处理。

–世界
18年8月24日在15:44

@worc状态实际上不会是不确定的,因为化径器在收到未定义的状态时会返回initialState

–纪尧姆
18年8月29日在14:30

@worc认为,使用这种方法时,每次有人创建新的reducer时,您都必须记住要添加重置案例。

– Francute
18/12/10在16:24

由于这两个原因,加上RESET_ACTION是动作的想法,我肯定已经改变了主意。因此从一开始它实际上并不属于减速器。

–世界
18/12/10在18:19

这绝对是正确的方法。将状态设置为除初始状态外的其他任何东西只是在自找麻烦

–塞巴斯蒂安·塞拉诺(Sebastian Serrano)
19年4月8日在15:08

#4 楼

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

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


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


评论


谢谢,这是我的工作,我来自Dan的回答,但我无法弄清楚。

–约翰·雅马罗(Aljohn Yamaro)
5月17日13:08

#5 楼

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

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

   return state
 }


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

#6 楼

使用Redux时,如果应用了以下解决方案,则假定我已在所有reducer中设置了initialState(例如{user:{name,email}})。在许多组件中,我都会检查这些嵌套的属性,因此通过此修复程序,可以防止我的render方法在耦合的属性条件下损坏(例如,如果上面提到的解决方案未定义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)
}


#7 楼

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

#8 楼

结合Dan,Ryan和Rob的方法来说明保持router状态并初始化状态树中的所有其他内容,我最终得出以下结论:

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


#9 楼

从安全角度来看,注销用户时最安全的操作是重置所有持久状态(例如cookie,localStorageIndexedDBWeb SQL等),并使用window.location.reload()硬刷新页面。可能是一个草率的开发人员意外地或有意地将某些敏感数据存储在window或DOM中,等等。吹散所有持久状态并刷新浏览器是确保没有任何信息从前一个用户泄露给下一个用户的唯一方法。 br />
(当然,作为共享计算机上的用户,您应该使用“私人浏览”模式,自己关闭浏览器窗口,使用“清除浏览数据”功能,等等,但是作为开发人员,我们可以不要指望每个人总是那么勤奋)

评论


为什么人们对此表示反对?当您使用空内容执行新的redux状态时,基本上您仍会在内存中保留以前的状态,并且从理论上讲您可以从中访问数据。刷新浏览器是您最安全的选择!

–威廉·索本(Wilhelm Sorban)
18/12/7在17:00



#10 楼

我已经创建了一个组件来赋予Redux重置状态的能力,您只需要使用此组件来增强您的商店并分派特定的action.type来触发重置。实现的思想与@Dan Abramov所说的相同。

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

#11 楼

我创建了清除状态的动作。因此,当我调度注销动作创建者时,我也会调度清除状态的动作。

用户记录动作

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


我不确定这是否是最佳选择吗?

评论


为什么不只在CLEAR_USER_RECORD情况下返回状态?

– Oliver D
9月6日17:49

#12 楼

我使用基于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"})
  }
}


#13 楼

如果您使用的是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的答案很好地说明了这个问题,但对我来说效果不佳,因为我使用的是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),但我受不了它,因为我喜欢元编程。

#14 楼

以下解决方案为我工作。

我向meta reducer添加了重置状态功能。关键是使用

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


#15 楼

被接受的答案帮助我解决了案件。但是,我遇到了必须清除非整体状态的情况。所以-我是这样做的:

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个以上的状态,但只想重置一个减速器的状态,该怎么办?

– Paul
2月14日下午13:49

#16 楼

我采取的措施是避免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;
  }
};


评论


将默认状态写为函数的想法在这里确实节省了一天。谢谢🙏

–克里斯·保罗
19年8月27日在8:37

#17 楼

使用Redux Toolkit和/或打字稿:
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);
};


#18 楼

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


#19 楼

一个对我有用的快速简便的选择是使用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'
})


希望有帮助

#20 楼

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

评论


如果您使用将商店同步到本地存储的中间件怎么办?那么您的方法根本行不通...

– Spock
16 Sep 10'在11:43

我真的不明白为什么人们会拒绝这样的答案。

–威廉·贾德(Wylliam Judd)
18年2月7日在17:06

为什么人们对此表示反对?当您使用空内容执行新的redux状态时,基本上您仍会在内存中保留以前的状态,并且从理论上讲您可以从中访问数据。刷新浏览器是您最安全的选择!

–威廉·索本(Wilhelm Sorban)
18/12/7在17:03

#21 楼

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

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


评论


如果只需要重置状态树的一部分,则还可以在该reducer中监听USER_LOGOUT并在那里进行处理。

– Andy_D
16年7月6日在15:46

#22 楼

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


注意:请确保将操作默认值设置为{},并且您可以,因为您不想遇到您在switch语句中检查action.type时出错。

#23 楼

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


#24 楼

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

#25 楼

在服务器中,我有一个变量是: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
});


#26 楼

以下解决方案对我有用。

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

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

从应用程序注销时,我们可以简单地重新分配默认状态,reducer将像新的一样工作。

主APP容器

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


主应用程序减速器

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 楼

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

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


#28 楼

可接受答案中的解决方案不做的一件事是清除参数化选择器的缓存。如果您有这样的选择器:

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

#29 楼

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

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


#30 楼

如果要重置单个减速器
例如



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