Lưu Redux state vào local storage với redux-persist

0
53
Rate this post
Video redux persist là gì

Redux Persist là một thư viện mạnh mẽ cho phép bạn lưu trữ Redux state vào các bộ nhớ nội bộ. Khi ứng dụng của bạn khởi động, nó sẽ lấy các state này ra và lưu trở lại vào Redux. Trong bài viết này, chúng ta sẽ tìm hiểu cách sử dụng redux-persist để lưu trữ state của bạn vào local storage.

Bắt đầu sử dụng

Đầu tiên, chúng ta cần cài đặt redux-persist bằng cách chạy lệnh sau:

npm install -save redux-persist

Sau đó, khi tạo Redux store, bạn cần truyền hàm persistReducer vào hàm createStore của bạn. Hàm persistReducer có tác dụng đóng gói reducer gốc trong ứng dụng của bạn. Sau khi store đã được khởi tạo, bạn cần truyền nó vào hàm persistStore để đảm bảo Redux state sẽ được lưu vào local storage mỗi khi nó thay đổi.

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

const persistConfig = {
  key: 'root',
  storage: storage,
};

const pReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(pReducer);
export const persistor = persistStore(store);

Nếu bạn đang sử dụng React, hãy bọc component gốc của bạn trong PersistGate. Điều này sẽ đảm bảo rằng quá trình render UI của ứng dụng sẽ bị trì hoãn cho đến khi state đã được lấy ra và lưu trở lại vào Redux.

import React from 'react';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/lib/integration/react';
import { persistor, store } from './store';
import { RootComponent, LoadingView } from './components';

const App = () => {
  return (
    <Provider store={store}>
      <PersistGate loading={<LoadingView />} persistor={persistor}>
        <RootComponent />
      </PersistGate>
    </Provider>
  );
};

export default App;

Tùy chỉnh những gì sẽ được lưu

Nếu bạn không muốn lưu một phần nào đó trong state, bạn có thể thêm nó vào danh sách blacklist. blacklist sẽ được thêm vào config object của persistReducer.

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

const pReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(pReducer);
export const persistor = persistStore(store);

blacklist nhận vào một mảng chuỗi, mỗi chuỗi tương ứng với một phần của state được quản lý bởi reducer mà bạn truyền vào persistReducer. Ví dụ, nếu rootReducer của bạn được tạo thông qua hàm combineReducers, bạn cần chắc chắn rằng navigation là một trong các reducer như sau:

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

Ngược lại, nếu bạn chỉ muốn lưu một phần nhất định trong state, bạn có thể sử dụng whitelist. Ví dụ, nếu bạn chỉ muốn lưu auth:

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

Nếu bạn muốn cấu hình để lưu một nested property, bạn có thể sử dụng PersistReducer. Ví dụ, nếu state của bạn có một key auth và bạn muốn lưu auth.currentUser nhưng không lưu auth.isLoggingIn, bạn có thể làm như sau:

// 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) => {
  // implementation của reducer
};

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

export default persistReducer(persistConfig, AuthReducer);

Nếu bạn muốn tất cả các cấu hình được đặt trong một nơi duy nhất, thay vì đặt chúng trong reducer tương ứng, bạn có thể thêm chúng vào hàm combineReducers như sau:

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

Quá trình merge

Khi ứng dụng của bạn khởi động, Redux sẽ thiết lập một state mặc định. Ngay sau đó, Redux Persist sẽ lấy state đã lưu từ local storage và ghi đè lên state mặc định.

Quá trình merge được thực hiện tự động cho bạn. Tuy nhiên, bạn cũng có thể có những xử lý riêng cho quá trình này. Ví dụ, trong các phiên bản cũ hơn của Redux Persist, bạn cần tự quản lý quá trình khôi phục state bằng cách bắt action REHYDRATE trong reducer và lưu payload của action này vào Redux state.

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,
      };
    // ... xử lý các action khác
  }
};

// Action REHYDRATE sẽ được dispatch bởi Redux Persist sau khi state đã được lấy từ local storage.
// Nếu bạn trả về một state object mới từ action REHYDRATE, đó sẽ là state cuối cùng bạn nhận được.
// Tuy nhiên, từ phiên bản mới của Redux Persist, bạn không cần phải bắt action này bằng tay nữa, trừ khi bạn muốn kiểm soát việc khôi phục state như thế nào.

Tuy nhiên, hãy cẩn thận khi sử dụng quá trình merge. Có một vấn đề bạn cần biết, đó là việc quá trình merge sẽ đến mức nào. Mặc định, quá trình merge chỉ thay đổi state ở cấp trên nhất. Nếu bạn thay đổi state mặc định, ví dụ như thêm một key error:

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

Nhưng state đã được lưu không có key error, nó sẽ thay đổi hoàn toàn state mặc định trong quá trình merge. Key error sẽ biến mất.

Để giải quyết vấn đề này, bạn cần yêu cầu PersistReducer merge sâu hai cấp. Trong phần “Bắt đầu sử dụng” ở trên, bạn đã thấy cách thiết lập stateReconciler trong root PersistReducer.

import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';

const persistConfig = {
  key: 'root',
  storage: storage,
  stateReconciler: autoMergeLevel2,
};

autoMergeLevel2 là cấu hình để merge sâu hai cấp. Đối với state auth, điều này có nghĩa là quá trình merge sẽ tạo một bản copy của state mặc định của auth, sau đó chỉ ghi đè keys trong object auth nếu chúng đã được lưu trữ. Vì key error chưa được lưu, nó sẽ được giữ nguyên.

Tóm lại, bạn cần nhớ rằng PersistReducers mặc định sẽ sử dụng autoMergeLevel1, tức là nó chỉ thay đổi state ở cấp trên nhất. Nếu bạn không có PersistReducer riêng để quản lý việc lưu trữ state cho các key ở cấp trên này, bạn cần sử dụng autoMergeLevel2. Tùy thuộc vào trường hợp của bạn, bạn có thể tự chỉnh cấp merge và không sử dụng hàm này.

Tùy biến nâng cao

Transforms

Transforms cho phép bạn tùy biến state object mà bạn đã lưu trữ và khôi phục.

Khi state object được lưu trữ, nó sẽ được serialize bằng JSON.stringify(). Nếu có những phần trong state object không thể map với JSON object, quá trình serialize có thể biến đổi những phần này theo những cách không lường trước được. Ví dụ, kiểu Set trong JavaScript không tồn tại trong JSON. Khi bạn serialize một Set bằng JSON.stringify(), nó sẽ trở thành một object rỗng. Điều này không phải là điều bạn muốn.

Dưới đây là một Transforms có thể lưu trữ một property thuộc kiểu Set đơn giản bằng cách chuyển đổi nó thành một mảng và ngược lại. Với cách này, Set sẽ được chuyển đổi thành Array, một cấu trúc dữ liệu được hỗ trợ trong JSON. Khi được khôi phục từ local storage, mảng sẽ được chuyển đổi ngược lại thành Set trước khi lưu vào Redux store.

import { createTransform } from 'redux-persist';

const SetTransform = createTransform(
  // transform state trước khi nó được serialize và lưu
  (inboundState, key) => {
    // chuyển đổi mySet thành một mảng
    return {
      ...inboundState,
      mySet: [...inboundState.mySet],
    };
  },
  // transform state đang được khôi phục
  (outboundState, key) => {
    // chuyển đổi mySet trở lại thành Set
    return {
      ...outboundState,
      mySet: new Set(outboundState.mySet),
    };
  },
  // định nghĩa reducer nào sẽ áp dụng transform này
  { whitelist: ['someReducer'] }
);

export default SetTransform;

Hàm createTransform nhận vào ba tham số:

  • Một hàm được gọi trước khi lưu state.
  • Một hàm được gọi trước khi khôi phục state.
  • Một config object.

Sau đó, bạn cần thêm transform vào config object của PersistReducer như sau:

import storage from 'redux-persist/lib/storage';
import SetTransform from './transforms';

const persistConfig = {
  key: 'root',
  storage: storage,
  transforms: [SetTransform],
};

// ... phần còn lại của implementation

Bài viết được dịch từ The Definitive Guide to Redux Persist của tác giả Mark Newton.

Bài viết được chỉnh sửa bởi: Dnulib.