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])