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

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

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

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

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

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

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

xyz

Ответы (36)

One way to do that would be to write a root reducer in your application.

Корневой редуктор обычно делегирует обработку действия редуктору, сгенерированному commonReducers (). Однако всякий раз, когда он получает действие 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 в качестве первого аргумента, независимо от действия. Давайте воспользуемся этим фактом, чтобы условно разделить накопленное состояние при передаче его в appReducer:

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

  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') {
    return appReducer(undefined, action)
  }

  return appReducer(state, action)
}

Если вы используете 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')

        return appReducer(undefined, action);
    }
    return appReducer(state, action);
};

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

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

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

Быстрый и простой вариант, который сработал для меня, использовал redux-reset. Это было несложно и также имеет некоторые дополнительные параметры для более крупных приложений.

Настройка в магазине создания

импорт reduxReset из 'redux-reset'
// ...
const enHanceCreateStore = составить (
    applyMiddleware (...),
    reduxReset () // Будет использовать RESET как действие по умолчанию. type для запуска сброса
) (createStore)
const store = enHanceCreateStore (редукторы)

Отправьте «сброс» в функцию выхода из системы

store.dispatch ({
    тип: 'СБРОС'
})

Если вы хотите сбросить единственный редуктор

Например

const initialState = {
  isLogged: ложь
}
// это будет ваше действие
export const resetReducer = () => {
  возвращение {
    тип: "СБРОС"
  }
}

экспорт по умолчанию (state = initialState, {
  тип,
  полезная нагрузка
}) => {
  switch (type) {
    // ваши действия придут ей
    case "RESET":
      возвращение {
        ...начальное состояние
      }
  }
}

// и из вашего интерфейса
отправка (resetReducer ())

ОБНОВЛЕНИЕ NGRX4

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

Это решение вдохновлено разделом API метаредукторов NGRX4 документации Github.

Во-первых, предположим, что вы комбинируете свои редукторы таким образом, используя новую опцию NGRX ActionReducerMap:

// index.reducer.ts
редукторы экспорта const: ActionReducerMap  = {
    auth: fromAuth.reducer,
    макет: fromLayout.reducer,
    пользователи: fromUsers.reducer,
    сети: fromNetworks.reducer,
    routingDisplay: fromRoutingDisplay.reducer,
    маршрутизация: fromRouting.reducer,
    маршруты: fromRoutes.reducer,
    routesFilter: fromRoutesFilter.reducer,
    параметры: fromParams.reducer
}

Теперь предположим, что вы хотите сбросить состояние из app.module

// app.module.ts
импортировать {IndexReducer} из './index.reducer';
импортировать {StoreModule, ActionReducer, MetaReducer} из '@ ngrx / store';
...
отладка функции экспорта (reducer: ActionReducer ): ActionReducer  {
    return function (состояние, действие) {

      switch (action.type) {
          case fromAuth.LOGOUT:
            console.log ("действие выхода");
            состояние = undefined;
      }
  
      редуктор возврата (состояние, действие);
    }
  }

  экспорт константных метаредукторов: MetaReducer  [] = [отладка];

  @NgModule ({
    импорт: [
        ...
        StoreModule.forRoot (редукторы, {metaReducers}),
        ...
    ]
})

экспортный класс AppModule {}

И это, по сути, один из способов добиться того же эффекта с NGRX 4.

The following solution worked for me.

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

return reducer(undefined, action);

, чтобы установить все редукторы в исходное состояние. Возврат undefined вместо этого вызывал ошибки из-за того, что структура хранилища была разрушена.

/ редукторы / index.ts

export function resetState(reducer: ActionReducer): ActionReducer {
  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[] = [ resetState ];

app.module.ts

import { StoreModule } from '@ngrx/store';
import { metaReducers, reducers } from './reducers';

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, { metaReducers })
  ]
})
export class AppModule {}

для меня лучше всего сработало initialState вместо state:

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

Дэн Абрамовs ответ правильный, за исключением того, что мы столкнулись со странной проблемой при использовании пакета response-router-redux вместе с этим подходом.

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

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

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

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

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

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

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

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

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

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

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

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

const RESET_ACTION = {
  type: "RESET"
}

Затем в каждом из ваших редукторов, если вы используете switch или if-else для обработки нескольких действий через каждый редуктор. Я возьму чехол для переключателя .

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

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

Если вы используете 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 для обработки редукторов.

Ответ Дэна дает отличное представление об этой проблеме, но для меня это не сработало, потому что я использую 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), но я не мог ' Не терплю, потому что я люблю метапрограммирование.

В сервере у меня есть переменная: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
});

Сначала при инициации нашего приложения состояние редуктора свежее и новое по умолчанию InitialState.

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

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

Основной контейнер приложения

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

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

Дан Абрамовответ помог мне раскрыть мой случай. Однако я столкнулся со случаем, когда нужно было очищать не все состояние. Так я и сделал:

const CombinedReducer = combReducers ({
    // мои редукторы
});

const rootReducer = (состояние, действие) => {
    if (action.type === RESET_REDUX_STATE) {
        // очищаем все, но сохраняем то, что хотим сохранить ..
        удалить state.something;
        удалить state.anotherThing;
    }
    returncommonReducer (состояние, действие);
}

экспорт rootReducer по умолчанию;

onLogout () {
  this.props.history.push ('/ логин'); // отправляем пользователя на страницу входа
  window.location.reload (); // обновите страницу
}

Одна вещь Дэн Абрамовответ не очищает кеш для параметризованных селекторов. Если у вас есть такой селектор:

экспорт const selectCounter1 = (state: State) => state.counter1;
экспорт const selectCounter2 = (состояние: Состояние) => state.counter2;
экспорт const selectTotal = createSelector (
  selectCounter1,
  selectCounter2,
  (счетчик1, счетчик2) => счетчик1 + счетчик2
);

Тогда вам нужно будет освободить их при выходе из системы следующим образом:

selectTotal.release ();

В противном случае мемоизированное значение для последнего вызова селектора и значения последних параметров все равно будут в памяти.

Примеры кода взяты из ngrx docs.

Чтобы сбросить состояние в исходное, я написал следующий код:

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

Вы можете обнулить данные редукторов, добавив этот код в файл действия,

сначала импортировать все типы:

import * as types from './types';

добавить этот код к действию выхода

for(let key of Object.values(types)) {
        dispatch({ type: key, payload: [] });
    }
npm install 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)

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

Подход с Redux Toolkit:


export const createRootReducer = (history: History) => {
  const rootReducerFn = combineReducers({
    auth: authReducer,
    users: usersReducer,
    ...allOtherReducers,
    router: connectRouter(history),
  });

  return (state: Parameters[0], action: Parameters[1]) =>
    rootReducerFn(action.type === appActions.reset.type ? undefined : state, action);
};

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

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

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) =>
  rootReducer(action.type === 'USER_LOGOUT' ? undefined : state, action);

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

// store

export const store: Store = createStore(
  rootReducer,
  storeEnhacer,
)

export const initialRootState = {
  ...store.getState(),
}

// root reducer

const appReducer = combineReducers(reducers)

export const rootReducer = (state: IStoreState, action: IAction) => {
  if (action.type === "USER_LOGOUT") {
    return appReducer(initialRootState, action)
  }

  return appReducer(state, action)
}


// auth service

class Auth {
  ...

  logout() {
    store.dispatch({type: "USER_LOGOUT"})
  }
}

Объединение Дана Абрамоваответа, Райана Ириллиответа и Роба Мурманаответ, чтобы учесть сохранение состояния маршрутизатора и инициализацию всего остального в дереве состояний, я получил следующее:

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

Просто расширение @ 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);
};
 const reducer = (state = initialState, { type, payload }) => {

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

   return state
 }

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

просто отредактируйте файл, в котором объявлены редукторы

import { combineReducers } from 'redux';

import gets from '../';

const rootReducer = (state, action) => {
  let asReset = action.type === 'RESET_STORE';

  const reducers = combineReducers({
    gets,
  });

  const transition = {
    true() {
      return reducers({}, action);
    },
    false() {
      return reducers(state, action);
    },
  };
  return transition[asReset] && transition[asReset]();
};

export default rootReducer;

Я хочу, чтобы 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;
  }
};

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

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

Использование Redux Toolkit и / или Машинопись:

const appReducer = combineReducers({
  /* your app’s top-level reducers */
});

const rootReducer = (
  state: ReturnType,
  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);
};

2022 WebDevInsider