Как reset состояние хранилища Redux?

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

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

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

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

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

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

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

Ответ 1

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

Корневой редуктор обычно combineReducers() делегирование обработки действия редуктору, сгенерированному combineReducers(). Однако всякий раз, когда он получает действие USER_LOGOUT, он снова возвращает исходное состояние.

Например, если ваш корневой редуктор выглядел так:

const rootReducer = combineReducers({
  /* your apps top-level reducers */
})

Вы можете переименовать его в appReducer и написать новый делегированный rootReducer:

const appReducer = combineReducers({
  /* your apps top-level reducers */
})

const rootReducer = (state, action) => {
  return appReducer(state, action)
}

Теперь нам просто нужно научить новый rootReducer возвращать начальное состояние после действия USER_LOGOUT. Как мы знаем, редукторы должны возвращать начальное состояние, когда они вызываются с undefined в качестве первого аргумента, независимо от действия. Давайте используем этот факт для условного appReducer накопленного 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 apps 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);
};

Ответ 2

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

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

Ответ 3

Определите действие:

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. Однако в обоих случаях вы можете отправить другое действие "СБРОС". Таким образом, выход из магазина и сброс будут происходить синхронно, и ваш магазин будет готов для входа другого пользователя.

Ответ 4

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

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

   return state
 }

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

Ответ 5

С Redux, если применил следующее решение, которое предполагает, что я установил initialState во всех моих редукторах (например, {user: {name, email}}). Во многих компонентах я проверяю эти вложенные свойства, поэтому с этим исправлением я предотвращаю, чтобы мои методы рендеринга были разбиты на свойства связанных свойств (например, если state.user.email, который будет вызывать пользователя ошибки, undefined, если вышеупомянутые решения).

const appReducer = combineReducers({
  tabs,
  user
})

const initialState = appReducer({}, {})

const rootReducer = (state, action) => {
  if (action.type === 'LOG_OUT') {
    state = initialState
  }

  return appReducer(state, action)
}

Ответ 6

Просто упрощенный ответ на лучший ответ:

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


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

Ответ 7

ОБНОВЛЕНИЕ NGRX4

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

Это решение основано на разделе API мета-редукторов документов GGR-Gubub NGRX4.

Во-первых, предположим, что вы комбинируете ваши редукторы, как это, используя новую опцию 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

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

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

Ответ 9

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

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

Ответ 10

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

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

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

Я не уверен, что это оптимально?

Ответ 11

Если вы используете redux-действия, здесь быстрый обходной путь с использованием 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 состояния не AsyncStorage поведение, поэтому я знал, что должен был вручную удалить элемент из хранилища (React Native в моем случае, таким образом, AsyncStorage).

await AsyncStorage.removeItem('persist:root');

или же

await persistor.flush(); // or await persistor.purge();

у меня тоже не получилось - они просто наорали на меня. (например, жалоба типа "Неожиданный ключ _persist...")

Затем я внезапно подумал, что все, что я хочу, это просто заставить каждый отдельный редуктор возвращать свое собственное начальное состояние при обнаружении типа действия RESET. Таким образом, упорство обрабатывается естественно. Очевидно, что без вышеуказанной служебной функции (handleActionsEx) мой код не будет выглядеть СУХИМ (хотя это всего лишь один RESET:() => initialState, т.е. RESET:() => initialState), но я не мог этого вынести, потому что я люблю метапрограммирование.

Ответ 12

Принятый ответ помог мне решить мой случай. Однако я столкнулся со случаем, когда необходимо очистить не все состояние. Итак - я сделал это так:

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;

Надеюсь, это поможет кому-то еще :)

Ответ 13

Просто расширение ответа @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);
};

Ответ 14

Следующее решение сработало для меня.

Я добавил функцию сброса состояния в мета редукторы. Ключ должен был использовать

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

Ответ 15

С точки зрения безопасности, наиболее безопасная вещь при выходе пользователя из системы - сбросить все постоянные состояния (например, cookie, localStorage, IndexedDB, Web SQL и т.д.) И выполнить IndexedDB обновление страницы с помощью window.location.reload(), Возможно, неаккуратный разработчик случайно или намеренно сохранил некоторые конфиденциальные данные в window, в DOM и т.д. Удаление всего постоянного состояния и обновление браузера - единственный способ гарантировать, что информация от предыдущего пользователя не будет передана следующему пользователю.

(Конечно, как пользователь на компьютере с общим доступом, вы должны использовать режим "частного просмотра", самостоятельно закрывать окно браузера, использовать функцию "очистки данных просмотра" и т.д., Но как разработчик мы не можем ожидать, что все будут всегда прилежный)

Ответ 16

Быстрая и простая опция, которая работала для меня, использовала 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'
})

Надеюсь это поможет

Ответ 17

Мое предположение, чтобы 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;
  }
};

Ответ 18

Этот подход очень прав: уничтожьте любое конкретное состояние "NAME", чтобы игнорировать и сохранять другие.

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

Ответ 19

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

Ответ 20

Я обнаружил, что принятый ответ хорошо работает для меня, но он вызвал ошибку 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)
}

Ответ 21

Мой обходной путь при работе с машинописью, основанной на ответе Дэна (избыточные типизации делают невозможным передачу undefined редуктору в качестве первого аргумента, поэтому я кеширую начальное корневое состояние в константу):

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

Ответ 22

В дополнение к ответу Дана Абрамова, мы не должны явно задавать действие как action = {type: '@@INIT'} рядом с state = undefined. При указанном выше типе действия каждый редуктор возвращает исходное состояние.

Ответ 23

на сервере у меня есть переменная: global.isSsr = true и в каждом редукторе у меня есть const: initialState Чтобы сбросить данные в хранилище, я делаю следующее с каждым редуктором: пример с 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
});

Ответ 24

Следующее решение работает для меня.

Во-первых, при запуске нашего приложения состояние редуктора является новым и новым по умолчанию InitialState.

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

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

Основной 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
    });
  }
}

Надеюсь, что это решит вашу проблему!

Ответ 25

Для того чтобы сбросить состояние до его начального состояния, я написал следующий код:

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

Ответ 26

Другой вариант:

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

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

Ответ 27

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