rtk/createAsyncThunk + Persist) 사용하기

김명성·2022년 7월 27일
0

rkt를 사용하면서 local/sesstion/indexDB에 값을 저장하게 만들어주는 Redux.persist를 createAsyncThunk와 연결하여 사용해보았다.

공식문서에는 예시가 간단 명료하게 나와있어 기존에 작성된 로직이 있다면 약간의 혼동을 받을 수 있었다.

store.ts - import

import {persistStore,persistReducer,FLUSH,REHYDRATE,PAUSE,PERSIST,PURGE,REGISTER} from 'redux-persist'
import localforage from 'localforage';

FLUSH,REHYDRATE ...은 Persist에서 제공하는 Action들이며 모두 불러오는 이유는 공식문서를 보니 rtk와의 충돌을 방지하기 위해서인것 같다.

store.ts - rootReducer,persistConfig 작성

혹시 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)

store.js - store 작성, 및 내보내기

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)

slice.ts - createAsyncThunk 작성

비동기처리가 필요하다면 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})
 
})

slice.ts - extraReducer 작성

  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의 값이 변동되게 분리할 수 있다.


persist 사용

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

0개의 댓글