본문 바로가기
Coding/Redux

Redux - Redux toolkit(with next js)

by z쿳쿳z 2022. 12. 17.
728x90
반응형

react 상태를 한곳에서 관리를 하기 위해서 redux, mobx, recoil과 같은 라이브러리를 사용하면 한곳에서 상태를 관리 할 수 있다. 하지만 redux 라이브러리를 사용하기 위해서는 store, reducer, action을 셋팅을 해서 사용을 해야한다. 그래서 오리려 작은 어플리케이션 같은 경우는 셋팅하는 공수가 더 큰 번거로움이 있다.

 

이를 보완하기 위해 redux-toolkit이 나왔고, 이전에 했던 redux 셋팅에 비해 간소해졌다. 최근 회사에서 redux를 사용하게 되면서 redux를 설치했더니, redux 에서 redux-toolkit을 권유하게 되었고 기존 셋팅에 비해 간소화 된 걸 확인 할 수 있었다.

 

redux를 사용하면 redux-thunk, redux-sage 아니면 rxjs 같은 미들웨어가 필요했는데 redux toolkit은 기본적으로 redux-toolkit을 내장하고 있다(셋팅할 때, 다른 미들웨어를 셋팅할 수 있다.)

 

# object

   - redux toolkit 사용해서 상태 관리(slice)

   - redux toolkit api 통신(extra reducer)

 

## Redux-toolkit setting

   * next-redux-wrapper도 버전 업이 되면서 이전 셋팅 방식과 달라짐

// 버전 7 미만일땐
export default wrapper.withRedux(MyApp)


// 버전 7 이상
const MyApp = ({pageProps}) => {
	const { store, props } = wrapper.useWrappedStore(pageProps);
    
    //...
}
export default MyApp
// store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import { rootReducer } from "../reducer";
import { createWrapper } from "next-redux-wrapper";

const makeStore = () => {
  const store = configureStore({ reducer: rootReducer });
  return store;
};

const store = makeStore();
const wrapper = createWrapper(makeStore);
export default wrapper;

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// reducer/index.ts
import { AnyAction, combineReducers } from "@reduxjs/toolkit";
import { HYDRATE } from "next-redux-wrapper";
import mainReducer from "./mainSlice";

const combinedReducers = combineReducers({
  main: mainReducer,
});

export const rootReducer = (state: any, action: AnyAction) => {
  switch (action.type) {
    case HYDRATE:
      return { ...state, ...action.payload };
    default:
      return combinedReducers(state, action);
  }
};
// _app.tsx
import type { AppProps } from 'next/app';
import wrapper from '../redux/store';
import { Provider } from 'react-redux';

function App({ Component, pageProps }: AppProps) {
  const { store, props } = wrapper.useWrappedStore(pageProps);

  return (
      <Provider store={store}>
        <Component {...props} />
      </Provider>
  );
}
export default App;

 

## Redux-toolkit 사용

   * slice 라는 새로운 메소드가 나왔다.

   * 이전에 action type과 reducer 함수를 따로 관리를 했지만, slice 한곳에서 같이 관리 할 수 있다.

   * createSlice를 통해서 slice를 만든다.

   * slice를 판별하는 name: key (unique name) - required

   * 상태 초기값 - required

   * reducer - reducer 함수

   * extraReducers - thunk 함수

   * slice를 통해서 actions와 reducer를 export     

import { createSlice } from "@reduxjs/toolkit";
const nodeSlice = createSlice({
  name: "nodeSlice",
  initialState,
  reducers: {
    changeNodePage(state, action) {
      state.currentPage = action.payload;
    },
    changeNodeNextPage(state, action) {
      state.pageNo = action.payload;
    },
  },
  extraReducers: {},
});
export const { changeNodePage, changeNodeNextPage } = nodeSlice.actions;

export default nodeSlice.reducer;

 

### 실제 사용 코드

   * createAsyncThunk를 통해서 api 통신

   * createAsyncThunk 첫번째 인자로 unique key 값 필요

   * 두번째 인자로 async callback 함수

   * dispatch(getNode({baseUrl, query})에서 getNode 인자가 thunk callback 인자로 들어온다

   callback 인자를 사용할 때 주의 할 점은 파라미터를 1개를 받는다(이걸로 헛짓많이함) 그래서 기본적인 string이나 number를 보낼 수 있고, 여러 인자를 보내려고 할떄는 객체로 만들어서 보내야한다.

   * getNode의 return 값이 extraReducer의 action.payload로 들어온다.

   * fullfilled는 통신 성공 / rejected는 error 발생

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
import { makeQueryString } from "../../utils";

interface IProps {
  baseUrl: string;
  query: IQuery;
}

interface IQuery {
  page_no: number;
  offset: number;
}

const initialState = {
  result: null,
  error: null,
  pageNo: 1,
  offset: 10,
  currentPage: 1,
};

export const getNode = createAsyncThunk(
  "getNode",
  async ({ baseUrl, query }: IProps) => {
    try {
      const queryString = makeQueryString(query);
      const response = await axios.get(`${baseUrl}/api/node?${queryString}`);
      if (response.status === 200) {
        return response.data;
      }
    } catch (error) {
      return error;
    }
  }
);

const nodeSlice = createSlice({
  name: "nodeSlice",
  initialState,
  reducers: {
    changeNodePage(state, action) {
      state.currentPage = action.payload;
    },
    changeNodeNextPage(state, action) {
      state.pageNo = action.payload;
    },
  },
  extraReducers: {
    [getNode.fulfilled.type]: (state, action) => {
      state.result = action.payload;
    },
    [getNode.rejected.type]: (state, action) => {
      state.result = initialState.result;
      state.error = action.error;
    },
  },
});
export const { changeNodePage, changeNodeNextPage } = nodeSlice.actions;

export default nodeSlice.reducer;
// 사용 예시
const dispatch = useDispatch();
dispatch(changeNodePage(1))
await dispatch(getNode({baseUrl: 'http://localhost:3000', query}))
728x90
반응형