상태관리를 위해 redux를, 비동기 통신을위한 미들웨어 (redux-saga, redux-thunk) 를 고민하던 중, thunk를 내장 하되 보다 편리한 사용환경을 제공하는 redux-toolkit 을 도입하기로 했다. 간단한 사용법에 대해 알아보자.
const instance = axios.create({
//// Option 1 : localhost
baseURL: "http://localhost:8080/", // jay
// Option 2 : fixed ip of backend local
// baseURL: "http://ipaddress:8080/",
//// Option 3 : backend server url
// baseURL: "https://www.google.com/api",
timeout: 3000,
});
export default instance;
먼저 통신을 위한 axios 를 커스텀 하여 사용했다. 서버 배포이전엔 백엔드 로컬 주소를 사용했고, 나중에는 백엔드로직을 다운받아서 내 로컬에서 사용했기 때문에 3가지 option을 번갈아가면서 썼다. baseurl만 스위치 해주면 custom instance 의 모든 주소가 일괄 변경되어 편리했다. 통신이 너무 길어지는 것을 방지하기 위해 3초가 지나면 timeout 하도록 했다.
analytics 관련 data 를 예시로 가져와 보겠다. 폴더구조는 다음과 같다.
utils < api < analytics
import axios from "utils/axiosConfig";
export const getAnalytics = async () => {
//the reason I made dataArr is because I failed to return data during axios
const dataArr = [];
await axios.get("/api/analytics").then((data: any) => {
dataArr.push(data.data);
});
return dataArr;
// Define a type for the slice state
interface ErrorMessage
errorMessage: string;
}
// Define a type of the report state
interface AnalyticsState {
error: null | string;
loading: boolean;
// allPages: AnalyticsValueState[];
allPages: AnalyticsValueState[];
}
//Define detail of analyticsValueState
interface AnalyticsValueState {
users: number;
points: number;
materialTypes: {
[key: string]: number;
};
}
//initial state
const initialState: AnalyticsState = {
error: null,
loading: false,
allPages: [],
};
//createAsyncThunk
//redux toolkit 에 내장된 createAsyncThunk을 활용하여, 비동기 통신 처리를 위한 세팅을 마친다.
export const fetchAnalytics = createAsyncThunk<
AnalyticsValueState[], // return type if it's fulfilled
string, // type of arg
{ rejectValue: ErrorMessage }// error 발생시 return 값
>("analytics/fetch"//action , async (arg, thunkAPI) => {
try {
return await getAnalytics(); // 요기에 비동기 통신로직을 넣음
} catch (e) {
return thunkAPI.rejectWithValue({
errorMessage: e.message,
});
}
});
//위와 같이 createAsyncThunk 를 선언하게 되면 첫번째 파라미터로 선언한 액션 이름에 pending, fulfilled, rejected 의 상태에 대한 action 을 자동으로 생성해주게 됩니다.
export const analyticsSlice = createSlice({
name: "analytics",
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
//this will be exported to independant action for state changing
},
extraReducers: {
[fetchAnalytics.pending.type]: (state) => { //호출전
state.error = null;
state.loading = true;
},
[fetchAnalytics.fulfilled.type]: (state, { payload }) => {// 성공
state.error = null;
state.loading = false;
state.allPages = payload;
},
[fetchAnalytics.rejected.type]: (state, { payload }) => {//실패
if (payload) state.error = payload.errorMessage;
state.loading = false;
},
},
});
//rootReducer.ts
//slice들을 합쳐준다.
const rootReducer = combineReducers({analyticSlice, ... })
//index.ts
// 합쳐진 rootreducer 를 store 에 담는다.
const store = configureStore({
reducer: rootReducer
});
//store의 state에 접근하는 useSelector
export const useSelector: TypedUseSelectorHook<RootState> = useReduxSelector;
//action 전달함수 useDispatch
export const useDispatch = () => useReduxDispatch<AppDispatch>();
//index.tsx
import { Provider as ReduxProvider } from "react-redux";
//provider를 통해 app 전역해서 store 에 접근할 수 있게 한다.
ReactDOM.render(
<ReduxProvider store={store}>
<App />
</ReduxProvider>)
참고: https://redux-toolkit.js.org/usage/usage-guide
redux-toolkit 을 사용해 redux 작성 하기 (createAction, createReducer, createSlice, createAsyncThunk)
Modern React Redux Toolkit - Login & User Registration Tutorial and Example