私は状態管理にReduxを使用しています。
どうやってストアを初期状態にリセットしますか?
たとえば、2つのユーザーアカウント(u1
とu2
)があるとします。
次の一連のイベントを想像してください。
ユーザーu1
がアプリにログインして何かをするので、ストアにデータをキャッシュします。
ユーザーu1
がログアウトします。
ユーザーu2
は、ブラウザを更新せずにアプリにログインします。
この時点で、キャッシュされたデータはu1
に関連付けられているので、クリーンアップしたいと思います。
最初のユーザーがログアウトしたときにReduxストアを初期状態にリセットする方法
そのための1つの方法は、アプリケーションにルートリデューサーを書くことです。
ルートリデューサーは通常、アクションの処理を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)
}
USER_LOGOUT
アクションの後に初期状態を返すように新しいrootReducer
を教える必要があります。知ってのとおり、リデューサーは、アクションに関係なく、最初の引数として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はあなたの状態のコピーをストレージエンジンに保存し、状態のコピーは更新時にそこからロードされます。
まず、適切な storage engine をインポートしてから、状態を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 Abramovによる受け入れられたコメントは、このアプローチと共にreact-router-reduxパッケージを使用したときに私たちが奇妙な問題を経験したことを除いて正しいことを指摘したいと思います。私たちの修正は、stateをundefined
に設定せず、現在のルーティングリデューサーを使用することでした。このパッケージを使用しているのであれば、以下の解決策を実装することをお勧めします。
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
const { routing } = state
state = { routing }
}
return appReducer(state, action)
}
アクションを定義します。
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
アクションを呼び出すときはいつでもこの方法で、あなたはデフォルトの状態でストアを更新するでしょう。
今、ログアウトのためにあなたは以下のように扱うことができます:
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」を送ることができます。このようにストアのログアウトとリセットは同期的に行われ、あなたのストアは別のユーザーログインの準備が整います。
const reducer = (state = initialState, { type, payload }) => {
switch (type) {
case RESET_STORE: {
state = initialState
}
break
}
return state
}
初期ストアにリセットしたい、すべてまたは一部のリデューサーによって処理されるアクションを起動することもできます。 1つのアクションで、自分の状態全体、または自分に合っていると思われる部分だけをリセットできます。これが最も簡単で制御しやすい方法だと私は思います。
Reduxでは次の解決策を適用しています。これは私のすべてのリデューサーにinitialStateを設定していることを前提としています(例:{user:{name、email}})。私はこれらのネストされたプロパティをチェックする多くのコンポーネントで、この修正で私は私のレンダリングメソッドが結合されたプロパティ条件で壊れるのを防ぎます。
const appReducer = combineReducers({
tabs,
user
})
const initialState = appReducer({}, {})
const rootReducer = (state, action) => {
if (action.type === 'LOG_OUT') {
state = initialState
}
return appReducer(state, action)
}
最良の答えに対する簡単な答えです。
const rootReducer = combineReducers({
auth: authReducer,
...formReducers,
routing
});
export default (state, action) => (
action.type === 'USER_LOGOUT'
? rootReducer(undefined, action)
: rootReducer(state, action)
)
更新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で同じ効果を達成するための一つの方法です。
Dan、Ryan、およびRobのアプローチを組み合わせて、router
状態を維持し、状態ツリー内の他のすべてを初期化することを考慮して、私はこれで終わりました。
const rootReducer = (state, action) => appReducer(action.type === LOGOUT ? {
...appReducer({}, {}),
router: state && state.router || {}
} : state, action);
Reduxに状態をリセットする機能を提供するためのコンポーネントを作成しました。ストアを強化し、リセットをトリガーするために特定のaction.typeをディスパッチするためにこのコンポーネントを使用する必要があります。実装の考え方は@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;
これが他の人に役立つことを願っています:)
状態をクリアするためのアクションを作成しました。そのため、ログアウトアクション作成者を派遣すると、状態をクリアするためのアクションも派遣されます。
ユーザーレコードアクション
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
}
};
これが最適かどうかわかりませんか。
次の解決策は私のために働いた。
私はメタリデューサーに状態をリセットする機能を追加しました。
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 {}
redux-actions を使用している場合は、handleActions
にHOF(Higher Order Function)を使用した簡単な回避策があります。
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();
私のためにも機能しませんでした - 彼らはただ私を怒鳴りました。 (例: "Unexpected key _persist ..." のように文句を言う
それから、RESET
というアクションタイプに遭遇したときに、すべての個々のリデューサーがそれぞれ独自の初期状態を返すようにすることだけを思いついた。そのようにして、固執は自然に処理されます。明らかに上記の効用関数(handleActionsEx
)がなければ、私のコードはDRYのようには見えません(ただし、それは単なる一行、つまりRESET: () => initialState
です)が、私はメタプログラミングが大好きです。
@ dan-abramov answerを拡張したもので、特定のキーがリセットされないようにする必要がある場合があります。
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'
})
お役に立てれば
なぜ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
をチェックするときにエラーが発生したくないため、問題ありません。
Dan Abramovの答えに加えて、state = undefinedの代わりにaction = {type: '@@ INIT'}としてactionを明示的に設定しないでください。上記のアクションタイプでは、すべてのリデューサーは初期状態を返します。
私は、受け入れられた答えが私のためにうまくいったことを見つけました、しかしそれは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)
}
しかし、そのコピーを作成する別のリデューサー関数に渡すために状態のコピーを作成するのは、少し複雑すぎるのでしょうか。これはそれほど見やすくはありませんが、もっと要点がわかります。
export default (state, action) => {
return appReducer(action.type === "LOGOUT" ? undefined : state, action)
}
サーバーでは、私は変数があります: global.isSsr = true そして各レデューサーで、私は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
});
このアプローチは非常に正しいです:他を無視して保つために任意の特定の状態「NAME」を破壊します。
const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
state.NAME = undefined
}
return appReducer(state, action)
}
Danの答えを基に構築された、TypeScriptを使用したときの私の回避策(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 appReducers = (state, action) =>
combineReducers({ reducer1, reducer2, user })(
action.type === "LOGOUT" ? undefined : state,
action
);
セキュリティの観点から、ユーザーをログアウトさせるときに行うべき最も安全なことは、すべての永続的な状態(たとえば、cookie、localStorage
、IndexedDB
、Web SQL
など)をリセットし、window.location.reload()
を使用してページをハードリフレッシュすることです。ずさんな開発者が誤ってまたは故意にDOMなどのwindow
に機密データを保存している可能性があります。すべての持続的な状態を消してブラウザを更新することが、前のユーザからの情報が次のユーザに漏洩しないことを保証する唯一の方法です。
(もちろん、共有コンピュータのユーザとしては、「プライベートブラウジング」モードを使用するか、ブラウザウィンドウを自分で閉じ、「クリアブラウジングデータ」機能などを使用する必要があります。ただし、開発者として常に誰もが期待できるわけではありません。その勤勉さ)
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;
}
};
次の解決策は私のために働きます。
私たちのアプリケーションの最初の開始/レデューサー状態はフレッシュおよび新規で、デフォルトは初期状態。
APP初期負荷を持続させるためにデフォルト状態を呼び出すアクションを追加する必要があります。
reAssigntheデフォルト状態とリデューサーはnewと同じように動作します。 。
メイン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
});
}
}
これがあなたの問題を解決することを願っています!