Answers:
一种方法是在您的应用程序中编写根减少器。
根减速器通常会将处理操作委托给由生成的减速器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
操作后返回初始状态即可。众所周知,无论以什么方式进行操作,都应该在将reduce 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);
};
case 'CLEAR_DATA': return initialState
export const createRootReducer = asyncReducers => { const appReducer = combineReducers({ myReducer ...asyncReducers }); return (state, action) => { if (action.type === 'LOGOUT_USER') { state = undefined; } return appReducer(state, action); } };
if (action.type === 'RESET') return action.stateFromLocalStorage
USER_LOGOUT
操作,则一旦触发该操作,是否有可能早日获取状态数据?(例如通过devtools)
我想指出的是,丹·阿布拉莫夫(Dan Abramov)接受的评论是正确的,除了在将react-router-redux软件包与这种方法一起使用时,我们遇到了一个奇怪的问题。我们的解决方法是不将状态设置为undefined
,而是仍然使用当前的路由减少器。因此,如果您使用的是此软件包,我建议您实施以下解决方案
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
const { routing } = state
state = { routing }
}
return appReducer(state, action)
}
router
and NOTrounting
combineReducers()
.....中定义的内容(如果您具有combineReducers({routing: routingReducer})
答案中所描述的内容)
定义一个动作:
const RESET_ACTION = {
type: "RESET"
}
然后,在您的每个化简器中,假设您正在使用switch
或if-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
为另一个答案更好。当您的应用程序期望状态树而您给它一个状态树时undefined
,除了空树之外,还有更多的错误和麻烦要处理。
只是最佳答案的简化答案:
const rootReducer = combineReducers({
auth: authReducer,
...formReducers,
routing
});
export default (state, action) =>
rootReducer(action.type === 'USER_LOGOUT' ? undefined : state, action);
使用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)
}
更新NGRX4
如果您要迁移到NGRX 4,则可能已从迁移指南中注意到中用于合并化简的rootreducer方法已被ActionReducerMap方法替换。首先,这种新的工作方式可能会使重置状态成为一个挑战。实际上,这很简单,但是方法已经改变。
该解决方案的灵感来自于 NGRX4 Github文档。
首先,假设您正在使用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达到相同效果的一种方法。
我已经创建了一个组件来赋予Redux重置状态的能力,您只需要使用此组件来增强您的商店并调度一个特定的action.type即可触发重置。实现的思想与@Dan Abramov所说的相同。
GitHub:https : //github.com/wwayne/redux-reset
我创建了清除状态的动作。因此,当我分派注销操作创建者时,我也会分派操作以清除状态。
用户记录动作
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
}
};
我不确定这是否最佳?
如果您使用的是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
来处理减速器。
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
),但是我受不了它,因为我喜欢元编程。
以下解决方案为我工作。
我在meta reducers中添加了重置状态功能,关键是要使用
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 {}
从安全的角度看,最安全的事情时,将用户登录了是将所有持久状态(如饼干,localStorage
,IndexedDB
,Web SQL
,等)和使用做网页的硬刷新window.location.reload()
。可能是一个草率的开发人员意外地或有意地将某些敏感数据存储window
在,DOM中等上。吹散所有持久状态并刷新浏览器是唯一保证不将前一个用户的信息泄露给下一个用户的方法。
(当然,作为共享计算机上的用户,您应该使用“私人浏览”模式,自己关闭浏览器窗口,使用“清除浏览数据”功能,等等,但是作为开发人员,我们不能期望每个人都永远勤奋)
我使用基于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"})
}
}
被接受的答案帮助我解决了案件。但是,我遇到了必须清除非整体状态的情况。所以-我这样做是这样的:
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;
希望这对其他人有帮助:)
只是@ 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);
};
一个对我有用的快速简便的选择是使用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'
})
希望这可以帮助
我采取的措施是防止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;
}
};
你为什么不只使用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
在切换语句内部检查时不希望遇到错误。
我发现,公认的答案为我工作很好,但它引发的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)
}
在服务器中,我有一个变量是:global.isSsr = true ,在每个reducer中,我都有一个const是:initialState 要重置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
});
以下解决方案对我有效。
首先在开始我们的应用程序的减速状态是新鲜的和新的默认的初始化状态。
我们必须添加一个操作,以调用APP初始加载以保持默认状态。
虽然应用程序的注销,我们可以简单的重新分配的默认状态和减速机的工作只是为新。
主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
});
}
}
希望这能解决您的问题!
可接受答案中的解决方案不做的一件事是清除参数化选择器的缓存。如果您有这样的选择器:
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。
只需让您的注销链接清除会话并刷新页面即可。您的商店不需要其他代码。每当您想完全重置状态时,页面刷新都是一种简单且易于重复的处理方式。
onLogout() {
this.props.history.push('/login'); // send user to login page
window.location.reload(); // refresh the page
}
window.location.reload();
window
,如反应母语