
rkt를 사용하면서 local/sesstion/indexDB에 값을 저장하게 만들어주는 Redux.persist를 createAsyncThunk와 연결하여 사용해보았다.
공식문서에는 예시가 간단 명료하게 나와있어 기존에 작성된 로직이 있다면 약간의 혼동을 받을 수 있었다.
import {persistStore,persistReducer,FLUSH,REHYDRATE,PAUSE,PERSIST,PURGE,REGISTER} from 'redux-persist'
import localforage from 'localforage';
FLUSH,REHYDRATE ...은 Persist에서 제공하는 Action들이며 모두 불러오는 이유는 공식문서를 보니 rtk와의 충돌을 방지하기 위해서인것 같다.
혹시 configureStore의 reducer에 작성한 리듀서가 있다면 모두 combineReducers로 옮겨놓도록 한다.
(옮기지 않는다면 set이 정상적으로 실행이 되지 않음 )
그 뒤 persistConfig를 작성하는데, 저장할 스토리지 (세션,로컬,indexDB)를 선택할 수 있고, blacklist,white 리스트를 입력해 저장할 값을 지정할 수도 있다.
두가지를 작성한 뒤에 persistReducer로 묶어 변수에 할당한다.
export const rootReducer = combineReducers({
cell:cellReducer,
bundle:bundleReducer,
})
const persistConfig = {
key: 'root',
version: 1,
storage:localforage,
whitelist:["cell"]
}
const persistedReducer = persistReducer(persistConfig,rootReducer)
reducer에 작성한 persistedReducer를 넣고 middleware를 작성한다.
ignoredActions를 통해 persist의 action들의 동작을 방지한다.
export const store = configureStore({
reducer:persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH,REHYDRATE,PAUSE,PERSIST,PURGE,REGISTER]
}
})
});
export let persistor = persistStore(store)
비동기처리가 필요하다면 cAT를 사용해야 한다.
error,loading,resolve는 따로 slice의 extrareducer에서 작성한다.
export const fetchCells = createAsyncThunk('cell/fetchCells',async () => {
const { data }: {data: Cell[]} = await axios.get('/cells');
return data
})
export const saveCells = createAsyncThunk('cell/saveCells', async () => {
const {cell: {data,order}} = store.getState();
const cells = order.map(id => data[id]);
// back에서 객체로 받는 걸로 작성. (local-api)
await axios.post('/cells',{cells})
})
extraReducers: (builder) => {
builder.addCase(fetchCells.fulfilled,(state ,action) => {
state.loading = false;
state.order = action.payload.map(cell => cell.id)
state.data = action.payload.reduce((acc,cell) => {
acc[cell.id] = cell;
return acc
},{} as CellState['data'])
})
builder.addCase(fetchCells.rejected, (state ,action) => {
const { message } = action.error
state.loading = false;
if ( message ){
state.error = message
} else {
state.error = '코드 전송 중 알 수 없는 에러가 발생했습니다.'
}
})
builder.addCase(fetchCells.pending, (state ,action) => {
state.loading = true
})
builder.addCase(saveCells.fulfilled, (state,action) => {
state.loading = false
})
builder.addCase(saveCells.pending, (state,action) => {
state.loading = true
})
builder.addCase(saveCells.rejected, (state,action) => {
const {message} = action.error
state.loading = false
if(message){
state.error = message
}
})
}
}
createSlice의 extraReducers 부분이다.
pending,fulfilled,rejected를 통해서 state의 값이 변동되게 분리할 수 있다.
import { Provider } from 'react-redux';
import {store,persistor} from './store/store'
import CellList from './components/CellList';
import './index.css';
import { PersistGate } from 'redux-persist/integration/react';
const App = () => {
return (
<>
<CellList />
</>
)
}
document.addEventListener('DOMContentLoaded', function (event) {
if (!container) {
container = document.getElementById('root') as HTMLElement;
const root = createRoot(container);
root.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
);
}
});
Provider 아래에 PersistGate를 통해 inject하는데, store에서 persistor가 지정될 때 까지 어플리케이션의 랜더가 지연된다.
따로 추가 로직을 구현할 필요없이 cAT를 dispatch 하면 persist가 감지하여 storage에 값을 저장한다
useEffect(() => {
dispatch(fetchCells())
dispatch(saveCells())
}, [dispatch])