reference: https://www.npmjs.com/package/redux-persist
Redux-Persist โ๏ธ

ํ๋ก์ ํธ ์งํ ์ค(=์ผํ ์นดํธ ๋ชฉ๋ก ํ์ด์ง), ์๋ก๊ณ ์นจ์ ์ํด ๋ฐ์ดํฐ๊ฐ ์ด๊ธฐํ๋๋ค๋ ๋ฌธ์ ์ ์ ๋ฐ๊ฒฌํ๋ค. Redux-Persist๋ ๋ธ๋ผ์ฐ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ์ ์๋๋ก ๋๋๋ค.
์ด์ปค๋จธ์ค ์ฌ์ดํธ์ '์ฅ๋ฐ๊ตฌ๋ ๋ชฉ๋ก' ๋๋ '๋คํฌ๋ชจ๋' ๋ฑ, ์ ์ ์ ๊ฐ์ธ์ ๋ณด ์ ์ถ์ ์ํฅ์ ๋ฏธ์น๋ ๋ฐ์ดํฐ๊ฐ ์๋๋ผ๋ฉด, ๋ธ๋ผ์ฐ์ ์ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ ๋ฆฌํ๊ฒ ๋ค.
๋ฐ๋ผ์ ๋ณธ TIL์์๋ Redux-Persist๊ฐ ์ ๊ณตํ๋ API ๋ช ๊ฐ์ง๋ฅผ ์ดํด๋ณด๊ณ ๊ธฐ์ด์ ์ธ ์ฌ์ฉ๋ฒ์ ๋ํด ๊ธฐ์ ํ๊ฒ ๋ค. ์ถ๊ฐ์ ์ผ๋ก ์ด์ ๊น์ง ์๋ํ shopping cart์ ๋ํ ์ ๋ฐ์ ์ธ ๋ก์ง์ ์ ๋ฆฌํ๋๋ก ํ๊ฒ ๋ค.
// configureStore.js
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web
import rootReducer from './reducers'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default () => {
let store = createStore(persistedReducer)
let persistor = persistStore(store)
return { store, persistor }
}
์ ์ฝ๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํ๋ก์ ํธ์ store.js ํ์ผ์ ์ ๋ ฅ๋ ๋ด์ฉ์ด๋ค. API์ ๋ํด์๋ ์ฐจํ ์ดํด๋ณผ ์์ ์ด๋, ๊ตฌ์กฐ๋ถํฐ ํ์ธํ๊ฒ ๋ค.
persistConfig๋ผ๋ ๊ฐ์ฒด๊ฐ ์ ์ธ๋์ด ์๊ณ ๋ด๋ถ์๋ 'key:'root''์ 'storage'๊ฐ ์๋ค. ์ด ๊ฐ์ฒด๋ฅผ persistReducer๋ผ๋ ํจ์์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ๊ณ ์๋ค. ๋ ๋ฒ์งธ ์ธ์์๋ rootReducer๊ฐ ์๋ค.
persistReducer ํจ์๋ฅผ persistedReducer์ ์ ์ฅํ๊ณ , persistedReducer๋ฅผ createStore ํจ์๋ฅผ ํตํด store์ ํ ๋นํ ๋ชจ์ต์ด๋ค.
์ถ๊ฐ์ ์ผ๋ก persistStore๋ผ๋ ํจ์์ ์ store๋ฅผ ์ธ์๋ก ์ ๋ฌํ์ฌ persistor๋ผ๋ ๋ณ์์ ํ ๋นํ๋ค.
import { PersistGate } from 'redux-persist/integration/react'
// ... normal setup, create store and persistor, import components etc.
const App = () => {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RootComponent />
</PersistGate>
</Provider>
);
};
๋ฃจํธ ์ปดํฌ๋ํธ๋ฅผ ์ ์ฝ๋์ฒ๋ผ PersistGate๋ก ๊ฐ์ธ๋ฉด ์ต์ข ์ ์ผ๋ก ๋ฐ์ดํฐ๊ฐ ๋ธ๋ผ์ฐ์ ์ ์ ์ฅ๋๋ค.
persistReducer()๋ ๋ ๊ฐ์ ์ธ์(=config, store)๋ฅผ ๋ฐ๋๋ค.
config obj

config ๊ฐ์ฒด๋ Redux-Persist์ ๋ํ ์ค์ ์ ํฌํจํ๋ ๊ฐ์ฒด๋ค. key๋ Redux ์ํ๋ฅผ ์๋ณํ ๋ ์ฌ์ฉํ key๋ค. storage๋ ์ ์ฅ์ ์ ํ(=๋ก์ปฌ ์คํ ๋ฆฌ์ง, ์ธ์ ์คํ ๋ฆฌ์ง)์ ์๋ฏธํ๋ค.
localStorage์ ์ ์ฅํ๊ณ ์ถ์ผ๋ฉด import storage from 'redux-persist/lib/storage', session Storage์ ์ ์ฅํ๊ณ ์ถ์ผ๋ฉด import storageSession from 'redux-persist/lib/storage/session'๋ฅผ ์์ฑํ๋ฉด ๋๋ค.
์์ฝํ์๋ฉด persistReducer()๋ Redux-Persist์ ๋ํ ์ค์ ์ ํฌํจํ๋ ๊ฐ์ฒด์ธ config๋ฅผ ์ ๋ฌ๋ฐ์์, Redux ์ํ๋ฅผ ์ง์์ ์ผ๋ก ์ ์ฅํ๊ณ ๋ณต์ํ ์ ์๋๋ก ํ๋ ์๋ก์ด reducer๋ฅผ ๋ฐํํ๋ ํจ์๋ผ๊ณ ํ ์ ์๋ค.

persistReducer()๋ฅผ ํตํด persist์ ๋ํ ์ค์ ์ด ์ ์ฉ๋ reducer๋ฅผ ๋ง๋ค์๋ค. persistStore()๋ ํด๋น ๋ด์ฉ์ ๋ด์์, persist์ ๋ํ ์ค์ ์ด ์ ์ฉ๋ store๋ฅผ ์์ฑํ๋ค. 'persistStore(store)'๋ persistor ๊ฐ์ฒด๋ค.
persistor ๊ฐ์ฒด๋ Redux Persist์ persistStore ํจ์๋ก๋ถํฐ ๋ฐํ๋๋ฉฐ, ์ํ๋ฅผ ์๊ตฌ์ ์ผ๋ก ์ ์ฅํ๊ณ ๋ณต์ํ๊ธฐ ์ํ ์ฌ๋ฌ ์ ์ฉํ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค. purge, flush, pause, persist๊ฐ ์๋๋ฐ ๋ณธ ๊ธ์์๋ ์์ธํ ๋ค๋ฃจ์ง ์๋๋ก ํ๊ฒ ๋ค. ์ ์ฉํ ๋ฉ์๋๊ฐ ์๋ค๋ ์ ๋ง ์์๋๊ณ ๋์ด๊ฐ์.
total ๊ด๋ จ ๋ก์ง โ๏ธ
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { splitPrice } from "../utils/commonModule";
export const getCartData = createAsyncThunk(
"cart/getCartData",
async (action) => {
console.log(action);
try {
return action;
} catch (error) {
console.error("Error: ", error);
throw error;
}
}
);
export const deleteCardData = createAsyncThunk(
"cart/deleteCardData",
async (productId) => {
try {
return productId;
} catch (error) {
console.error("Error: ", error);
throw error;
}
}
);
export const updateTotalPriceAndQuantity = createAsyncThunk(
"cart/updateTotalPriceAndQuantity",
async (_, { getState }) => {
const state = getState();
const { cartProduct } = state.cart;
const { totalPrice, totalQuantity } = cartProduct.reduce(
(acc, item) => {
acc.totalPrice += splitPrice(item.price) * item.count;
acc.totalQuantity += item.count;
return acc;
},
{ totalPrice: 0, totalQuantity: 0 }
);
return { totalPrice, totalQuantity };
}
);
const cartSlice = createSlice({
name: "cart",
initialState: {
cartProduct: [],
totalPrice: 0,
totalQuantity: 0,
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(getCartData.fulfilled, (state, action) => {
const { category, id, count } = action.payload;
const existingProductIndex = state.cartProduct.findIndex(
(product) => product.category === category && product.id === id
);
if (existingProductIndex !== -1) {
state.cartProduct[existingProductIndex].count += count;
} else {
state.cartProduct.push(action.payload);
}
});
builder.addCase(deleteCardData.fulfilled, (state, action) => {
const { id, category } = action.payload;
const deleteIndex = state.cartProduct.findIndex(
(product) => product.category === category && product.id === id
);
if (deleteIndex !== -1) {
state.cartProduct.splice(deleteIndex, 1);
}
});
builder.addCase(updateTotalPriceAndQuantity.fulfilled, (state, action) => {
const { totalPrice, totalQuantity } = action.payload;
state.totalPrice = totalPrice;
state.totalQuantity = totalQuantity;
});
},
});
export default cartSlice.reducer;
์ด์ ์ง์ง ์๋ฒฝํ๊ฒ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค. ํ์ง๋ง ๋ด์ผ์ด ์ํ์ด๊ธฐ์...
view๊น์ง ์๋ฒฝํ๊ฒ ๊ตฌ์ฑํด์ ์ต์ข ์ ์ผ๋ก ์ ๋ฆฌํ๋๋ก ํ๊ฒ ๋ค!
