2016-02-25 5 views
178

Я использую Redux для управления состоянием.
Как вернуть хранилище в исходное состояние?Как восстановить состояние хранилища Redux?

Например, у меня есть две учетные записи пользователей (u1 и u2).
Представьте себе следующую последовательность событий:

  1. пользователь u1 журналов в приложение, и делает что-то, поэтому мы кэшировать некоторые данные в хранилище.

  2. Пользователь u1 выходит из системы.

  3. Пользователь u2 регистрируется в приложении без обновления браузера.

На данный момент, кэшированные данные будут связаны с u1, и я хотел бы, чтобы очистить его.

Как сбросить хранилище Redux до его начального состояния, когда первый пользователь выходит из системы?

+2

Возможно, лучше очистить состояние при выходе из системы (с точки зрения безопасности) – Clarkie

+1

Не редактируйте свой вопрос, чтобы включить решение. Принять ответ достаточно. Вы также можете самостоятельно ответить на свое конкретное решение, если почувствуете, что добавили много материала к существующим ответам. Тем временем я удалил часть вопросов, не относящуюся к теме. – ryanyuyu

ответ

433

Один из способов сделать это - написать корневой редуктор в своем приложении.

Корневой редуктор обычно делегирует обработку воздействия на редуктор, генерируемый 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 действий. Как мы знаем, редукторы должны возвращать исходное состояние, когда их вызывают с 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) 
} 

Обратите внимание, что я не мутирует состояние здесь, я просто reassigning the reference локальной переменной называется state перед передачей его в другую функцию. Мутирование государственного объекта будет нарушением принципов Redux.

В случае использования redux-persist, вам также может потребоваться очистить хранилище.Redux-perist хранит копию вашего состояния в системе хранения, и они будут загружены оттуда при обновлении.

Сначала необходимо импортировать соответствующий 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); 
}; 
+6

Мне любопытно Дэн, не могли бы вы также сделать что-то подобное в своем редукторе. с CLEAR_DATA, являющимся действием. 'case 'CLEAR_DATA': return initialState' – HussienK

+3

@HussienK, который работал бы, но не на состоянии для каждого редуктора. –

+7

Вот версия, где вы динамически комбинировать восстановители в случае, если вы используете асинхронные восстановители: 'экспорт константного createRootReducer = asyncReducers => { константного appReducer = combineReducers ({ myReducer ... asyncReducers }); return (state, action) => { if (action.type === 'LOGOUT_USER') { state = undefined; } return appReducer (состояние, действие); } }; ' –

-11

Просто отправьте ссылку на выход из системы и обновите страницу. Дополнительный код не нужен для вашего магазина. Каждый раз, когда вы хотите полностью перезагрузить состояние, обновление страницы - это простой и легко повторяемый способ его обработки.

+0

Что делать, если вы используете промежуточное программное обеспечение, которое синхронизирует хранилище с localstorage? Тогда ваш подход не работает вообще ... – Spock

+0

Я не совсем понимаю, почему люди с подобным ответом отвечают так. –

2

Я создал компонент, чтобы дать Redux возможность сброса состояния, вам просто нужно использовать этот компонент для улучшения вашего хранилища и отправки определенного типа action.type для запуска сброса. Мысль о реализации такая же, как и то, что сказал @Dan Abramov.

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

50

Я хотел бы отметить, что принятый комментарий Дэн Абрамов является правильным, за исключением мы испытали странный вопрос при использовании пакета реагировать маршрутизаторами-перевождь наряду с этим подходом. Наше исправление заключалось в том, чтобы не устанавливать состояние в undefined, а скорее использовать текущий редуктор маршрутизации. Поэтому я хотел бы предложить внедрение решения ниже, если вы используете этот пакет

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

Я думаю, что вытаскивание здесь состоит в том, что вы можете не захотеть очистить все дерево состояний при выходе из системы - этот подход работает одинаково хорошо в корневом редукторе любого поддерева, и поэтому может быть яснее применить этот метод только для корневых редукторов поддерево (ы) вы * сделаете * хотите очистить, а не выделять «особые» дети на * не * очистить корневой редуктор всего дерева, например, – davnicwil

+0

Я думаю, что у меня возникают эти проблемы, которые вы имеете в виду теперь (где при выходе из системы он будет устанавливать маршрут на правильный путь, но будет загружен полный компонент). Я применил что-то похожее на ваше, чтобы исправить его, но я думаю, что что-то с неизменяемыми js массирует это.Я закончил создание родительского редуктора, у которого есть действие RESET-STATE, и я наследую его от этого редуктора, чтобы не дотрагиваться до маршрутизации. –

+0

Имел аналогичные проблемы, это исправило это. Благодарю. –

0

Этот подход очень верно: Разрушу какого-либо конкретного состояния «NAME», чтобы игнорировать и сохранить другие.

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

Если вам нужно только сбросить один кусок дерева состояний, вы также можете прослушивать' USER_LOGOUT' в этом редукторе и обрабатывать его там , –

5

С Redux, если применил следующее решение, которое предполагает я установил InitialState во всех моих восстановителях (например, {пользователь: {имя, адрес электронной почты}}). Во многих компонентах я проверяю эти вложенные свойства, поэтому с этим исправлением я предотвращаю, чтобы мои методы рендеринга были разбиты на связанные свойства (например, если 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) 
} 
3

Объединяя подходы Дэн, Райан и Роб, на счет для поддержания router состояния и инициализации все остальное в состоянии дерева, я закончил с этим:

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

я создал действия чтобы очистить состояние. Поэтому, когда я отправляю создателя действия выхода из системы, я отправляю действия, чтобы очистить состояние.

записи пользователя Действие

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

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

I A m Не уверен, что это оптимально?

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

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

    return state 
} 

Вы также можете запустить действие, которое обрабатывается всеми или некоторыми редукторами, которые вы хотите сбросить до первоначального хранилища. Одно действие может инициировать сброс в ваше состояние, или просто часть его, которая вам подходит.Я считаю, что это самый простой и контролируемый способ сделать это.

0

почему бы вам не просто использовать 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 внутри оператора switch.

-1

Другой вариант заключается в следующем:

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

'@@redux/INIT' типа действия, которые Redux депеши автоматически при createStore, поэтому предполагается, что ваши восстановители все они по умолчанию уже, это поймают те, и начать свое состояние от свежий. Это может рассматриваться как частная деталь реализации Redux, хотя, так что покупатель остерегается ...

+0

Я сделал это не меняя состояние, также я попробовал @@ INIT, который показан в ReduxDevtools как первое действие – RezaRahmati

1

UPDATE NGRX4

Если вы переходите на NGRX 4, вы, возможно, заметили из migration guide, что метод rootreducer для объединения ваши редукторы были заменены методом ActionReducerMap. Во-первых, этот новый способ сделать вещи может сделать проблему сброса проблемой. Это действительно прямолинейно, но способ сделать это изменился.

Это решение навеяно API разделе мета-переходников из NGRX4 Github docs.

Во-первых, позволяет сказать, что ваш совмещают свои восстановителей, как это с помощью новой опции ActionReducerMap NGRX в:

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

Каждый раз userlogs в, без обновления браузера. Магазин всегда будет по умолчанию.

store.dispatch(RESET_ACTION) только уточняет идею. У вас скорее всего будет создатель действия для этой цели. Лучше всего будет то, что у вас есть LOGOUT_ACTION.

После отправки этого LOGOUT_ACTION. Пользовательское промежуточное программное обеспечение может затем перехватить это действие либо с помощью Redux-Saga, либо с Redux-Thunk. Однако в обоих случаях вы можете отправить другое действие «СБРОС». Таким образом, выход из магазина и сброс произойдет синхронно, и ваш магазин будет готов для входа в другой пользователь.

-1

Я просто вызываю действие, которое вызывает window.location.reload(), который перезагружает окно браузера и устанавливает состояние сброса.

Смежные вопросы