Полное руководство по Redux Persist

Сохраняйте состояние Redux между запусками приложений с помощью Redux Persist

Redux Persist берет ваш объект состояния Redux и сохраняет его в постоянное хранилище. Затем при запуске приложения он извлекает это постоянное состояние и сохраняет его обратно в redux.

Примечания:

  • Это руководство предназначено для версии 5 redux-persist, выпущенной в октябре 2017 года.
  • Части этого руководства были объединены в официальную документацию с помощью запроса на перенос this, который я отправил. Тем не менее, это руководство по-прежнему является вашим лучшим источником для понимания библиотеки.

Быстрый старт

Зависимости

npm install --save redux-persist - OR - yarn add redux-persist

Реализация

При создании хранилища redux передайте функции createStore persistReducer, который обертывает корневой редуктор вашего приложения. Как только ваш магазин будет создан, передайте его функции persistStore, которая гарантирует, что ваше состояние redux сохраняется в постоянное хранилище при каждом изменении.

// src/store/index.js

import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import rootReducer from './reducers'; // the value from combineReducers

const persistConfig = {
 key: 'root',
 storage: storage,
 stateReconciler: autoMergeLevel2 // see "Merge Process" section for details.
};

const pReducer = persistReducer(persistConfig, rootReducer);

export const store = createStore(pReducer);
export const persistor = persistStore(store);

Если вы используете React, оберните корневой компонент PersistGate. Это задерживает рендеринг пользовательского интерфейса вашего приложения до тех пор, пока ваше постоянное состояние не будет получено и сохранено в redux.

import React from 'react';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/lib/integration/react';

// import the two exports from the last code snippet.
import { persistor, store } from './store';
// import your necessary custom components.
import { RootComponent, LoadingView } from './components';

const App = () => {
  return (
    <Provider store={store}>
      // the loading and persistor props are both required!
      <PersistGate loading={<LoadingView />} persistor={persistor}>
        <RootComponent />
      </PersistGate>
    </Provider>
  );
};

export default App;

Настройка того, что сохраняется

Если вы не хотите сохранять часть своего состояния, вы можете поместить ее в черный список. Черный список добавляется в объект конфигурации, который мы использовали при настройке нашего PersistReducer.

const persistConfig = {
  key: 'root',
  storage: storage,
  blacklist: ['navigation']
};

const pReducer = persistReducer(persistConfig, rootReducer);

export const store = createStore(pReducer);
export const persistor = persistStore(store);

Черный список принимает массив строк. Каждая строка должна соответствовать части состояния, управляемой редуктором, который вы передали persistReducer. В приведенном выше примере, если rootReducer был создан с помощью функции combReducers, мы ожидаем, что там появится навигация, например:

combineReducers({ 
  auth: AuthReducer,
  navigation: NavReducer, 
  notes: NotesReducer
});

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

const persistConfig = {
  key: 'root',
  storage: storage,
  whitelist: ['auth', 'notes']
};

Что, если вы хотите внести вложенное свойство в черный список? Например, предположим, что у вашего объекта состояния есть ключ аутентификации, и вы хотите сохранить auth.currentUser, но НЕ auth.isLoggingIn.

Для этого оберните ваш AuthReducer PersistReducer, а затем внесите isLoggingIn в черный список. Это позволяет вам совмещать ваши правила сохраняемости с редуктором, к которому они относятся.

// AuthReducer.js
import storage from 'redux-persist/lib/storage';
import { persistReducer } from 'redux-persist';

const INITIAL_STATE = {
  currentUser: null,
  isLoggingIn: false
};

const AuthReducer = (state = INITIAL_STATE, action) => {
  // reducer implementation
};

const persistConfig = {
  key: 'auth',
  storage: storage,
  blacklist: ['isLoggingIn']
};

export default persistReducer(persistConfig, AuthReducer);

Если вы предпочитаете, чтобы все ваши правила сохраняемости были в одном месте, а не вместе с соответствующим редуктором, подумайте о том, чтобы поместить все это вместе с вашей combineReducers функцией:

// src/reducers/index.js

import { combineReducers } from 'redux';
import storage from 'redux-persist/lib/storage';
import { persistReducer } from 'redux-persist';

import { authReducer, navReducer, notesReducer } from './reducers'

const rootPersistConfig = {
  key: 'root',
  storage: storage,
  blacklist: ['navigation']
};

const authPersistConfig = {
  key: 'auth',
  storage: storage,
  blacklist: ['isLoggingIn']
};

const rootReducer = combineReducers({
  auth: persistReducer(authPersistConfig, authReducer),
  navigation: navReducer,
  notes: notesReducer
});

export default persistReducer(rootPersistConfig, rootReducer);

Процесс слияния

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

Процесс слияния предназначен для того, чтобы «просто работать» автоматически. Однако вы также можете взять под контроль процесс вручную. Например, в более старых версиях Redux Persist было обычным делом управлять процессом регидратации, перехватывая действие REHYDRATE в редукторах, а затем сохраняя полезную нагрузку действия в состояние редукции.

import { REHYDRATE } from 'redux-persist';

const INITIAL_STATE = {
  currentUser: null,
  isLoggingIn: false
};

const AuthReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
      
    case REHYDRATE:
      return {
        ...state,
        currentUser: action.payload.currentUser 
      };
      
    // ...handle other cases

Действие REHYDRATE отправляется Redux Persist сразу после того, как ваше постоянное состояние получено из хранилища. Если вы вернете новый объект состояния из действия REHYDRATE, это будет ваше завершенное состояние. Как уже упоминалось, вам больше не нужно этого делать, если вам не нужно настраивать способ регидратации вашего состояния.

Остерегайтесь огромной ошибки ...

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

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

// initial state
{ 
  auth: {
    currentUser: null,
    isLoggingIn: false
  },
  notes: [] 
}

Наше приложение запускается, и вот наше постоянное состояние.

// persisted state
{
  auth: {
    currentUser: { firstName: 'Mark', lastName: 'Newton' },
    isLoggingIn: false
  },
  notes: [noteA, noteB, noteC]
}

По умолчанию процесс слияния просто заменяет каждую часть состояния верхнего уровня. В коде это выглядит примерно так:

const finalState = { ...initialState };
finalState['auth'] = persistedState['auth']
finalState['notes'] = persistedState['notes']

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

const INITIAL_STATE = {
  currentUser: null,
  isLoggingIn: false,
  error: ''
};

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

Чтобы исправить это, нужно сказать вашему PersistReducer объединить два уровня в глубину. В разделе «Быстрый запуск» вы могли заметить загадочную stateReconciler настройку для нашего корневого PersistReducer.

import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
const persistConfig = {
 key: 'root',
 storage: storage,
 stateReconciler: autoMergeLevel2
};

autoMergeLevel2 - это то, как вы глубоко сливаете два уровня. Для состояния аутентификации это означает, что процесс слияния сначала сделает копию исходного состояния аутентификации, а затем только переопределит ключи в этом объекте аутентификации, которые были сохранены. Поскольку «ошибка» еще не сохранится, ее оставят в покое.

Таким образом, важно знать, что для PersistReducers по умолчанию установлено значение autoMergeLevel1, что означает, что они заменяют состояние верхнего уровня тем, что было сохранено. Если у вас нет отдельного PersistReducer, управляющего постоянным состоянием для этих ключей верхнего уровня, вы, вероятно, захотите использовать autoMergeLevel2.

Интересный лакомый кусочек: автор Redux Persist понял, что выбор между autoMergeLevel1 и autoMergeLevel2 может сбивать с толку. Поэтому он создал функцию под названием persistCombineReducers в попытке упростить ситуацию. Реализация этой функции представляет собой две строки кода и просто возвращает PersistReducer, значение по умолчанию для которого равно autoMergeLevel2. Я лично предпочитаю устанавливать уровень слияния и не использовать эту функцию. Но решать только вам.

Расширенная настройка

Трансформирует

Преобразования позволяют настраивать объект состояния, который сохраняется и восстанавливается.

Когда объект состояния сохраняется, он сначала сериализуется с JSON.stringify(). Если части вашего объекта состояния не отображаются на объекты JSON, процесс сериализации может неожиданным образом преобразовать эти части вашего состояния. Например, в JSON не существует типа javascript Set. Когда вы пытаетесь сериализовать Set через JSON.stringify()​, он преобразуется в пустой объект. Наверное, не то, что вы хотите.

Ниже приведено преобразование, которое успешно сохраняет свойство Set, которое просто преобразует его в массив и обратно. Таким образом Set преобразуется в массив, который является распознанной структурой данных в JSON. При извлечении из постоянного хранилища массив преобразуется обратно в Set перед сохранением в хранилище redux.

import { createTransform } from 'redux-persist';

const SetTransform = createTransform(
  
  // transform state on its way to being serialized and persisted.
  (inboundState, key) => {
    // convert mySet to an Array.
    return { ...inboundState, mySet: [...inboundState.mySet] };
  },
  
  // transform state being rehydrated
  (outboundState, key) => {
    // convert mySet back to a Set.
    return { ...outboundState, mySet: new Set(outboundState.mySet) };
  },
  
  // define which reducers this transform gets called for.
  { whitelist: ['someReducer'] }
);

export default SetTransform;

Функция createTransform принимает три параметра.

  • Функция, которая вызывается прямо перед сохранением состояния.
  • Функция, которая вызывается прямо перед регидратацией состояния.
  • Объект конфигурации.

Наконец, необходимо добавить преобразования к объекту конфигурации PersistReducer.

import storage from 'redux-persist/lib/storage';
import { SetTransform } from './Transforms';
const persistConfig = {
  key: 'root',
  storage: storage,
  transforms: [SetTransform]
};
// ...remaining implementation

На этом пока все ...

Есть вопросы? Оставьте мне комментарий!