티스토리 뷰

반응형

redux-toolkit이란 redux를 조금 더 쉽게 사용하기 위해 나온 라이브러리이다. 이 글에서는 개념보다는 쉽게 프로젝트에 redux를 적용할 수 있도록 코드 위주로 설명해 보겠다. 구현 정도는 redux로 전역 변수를 관리하는 부분까지 진행할 예정이다. 왜냐하면 Redux-Saga나 Redux-thunk 같은 미들웨어는 최근 프런트 진영에서 포일러플레이트 이슈로 잘 사용하지 않고, 대신 react-query, SWR 같은 라이브러리는 사용하는 추세이기 때문이다. 하지만, react-query나 SWR은 서버와 동기화를 위해 사용되는 것이기 때문에, 완전한 전역변수 관리는 불가능하다. 따라서 Frontend 자체의 전역변수 관리는 Redux나 Recoil을 사용하는 것이 좋다.

 

 

 

 

redux-toolkit 설치

우선 설치하는 명령어는 아래와 같다. react-redux에 toolkit을 추가 설치한다.

 

$ npm install @reduxjs/toolkit react-redux

 

TypeScript을 사용 중이라면, 아래의 라이브러리를 추가 설치하자. 아래 코드는 TypeScript로 예시가 작성되어 있다. 만약 JavaScript로 코드를 만든다면, 타입 부분만 삭제해 주면 된다.

 

$ npm install @types/react-redux

 

 

redux-toolkit 만들기

우선 root 위치에 redux 폴더를 만들자. 그리고 redux 폴더 내부에 store.ts 파일을 만든다. store.ts 내부의 코드는 아래와 같다.

 

// redux/store.ts

import { combineReducers, configureStore } from "@reduxjs/toolkit";

const rootReducer = combineReducers({});

const store = configureStore({
  reducer: rootReducer,
});

export type RootState = ReturnType<typeof store.getState>;

export default store;

 

사실 위의 코드만 설정을 해도 redux store 설정은 완료된다. 하지만 조금 더 추가 설정을 해보자. 먼저 non-serializable 타입을 Redux에 저장할 수 있도록 해보자. 아래와 같이 middleware를 추가하여 serializableCheck를 false로 설정하면 된다. 설정을 추가한 코드는 아래와 같다.

 

import { combineReducers, configureStore } from "@reduxjs/toolkit";

const rootReducer = combineReducers({});

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }),
});

export type RootState = ReturnType<typeof store.getState>;

export default store;

 

위의 코드를 확인해 보면, middleware 부분이 추가된 것을 알 수 있다. 관련 부분을 조금 더 설명해 보겠다. 기본적으로 JavaScript에서는 객체 타입을 많이 쓴다. 하지만, 우리가 데이터를 브라우저의 localStorage에 저장하려 한다면 객체 자체를 바로 추가하는 것은 불가능하다. 왜냐하면 localStorage는 string 값만 들어갈 수 있기 때문이다. 따라서 JavaScript는 object 타입을 string으로 변환하여 저장을 하는데, 이를 serialization(직렬화)라고 한다. 반대로 localStorage에서 값을 빼낸 후에 JavaScript에서 string 값을 객체로 변환하는 작업은 deserialization(역직렬화)하고 한다. 정리하면, serializableCheck 설정이란, Redux의 기본 설정은 action에 직렬화 불가능한 값을 허용한다고 이해하면 된다.

 

Chrome extension에서 redux를 설치하면, 아래와 같이 개발자 도구에서 내가 만든 리덕스를 파악하는 것이 가능하다. 이것은 개발 시 많은 도움을 준다. 하지만, product 배포 환경에서는 아래의 개발 툴은 필요가 없다.

 

아래의 devTools 설정으로 개발자를 배포 라이브 환경에서 보이지 않게 할 수 있다. store 변수 내용만 아래와 같이 변경해 주면 된다.

 

const store = configureStore({
  reducer: rootReducer,
  devTools: process.env.NODE_ENV !== "production", // 개발자도구 확인
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }),
});

 

devTools 부분이 false라면, 아래의 그림처럼 개발자도구에서 Redux를 내용을 확인할 수 없게 된다.

 

 

리덕스는 페이지를 새로고침을 하면 내용이 없어진다. 리덕스에 로그인 정보를 넣어두었다면, 새로고침 시 정보가 사라져서 계속 로그인을 해야 하는 상황이 일어난다. 이러한 이슈를 해결하기 위해서 redex-persist 설정을 추가해 보자. redex-persist 설정은 브라우저 저장소 중에 localStorage/SessionStorage에 리덕스 내용을 저장하여 새로고침 시 데이터 손실을 막는다. 관련 자세한 내용은 여기서는 다루지 않는다. 우선 redex-persist을 설치하자.

 

$ npm i redex-persist

 

설치 후에 아래와 같이 설정을 추가해 준다.

 

import { combineReducers, configureStore } from "@reduxjs/toolkit";
import storage from "redux-persist/lib/storage";

import { persistReducer, persistStore } from "redux-persist";

const persistConfig = {
  key: "root",
  storage,
  whitelist: [""],
};

const rootReducer = combineReducers({});

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = configureStore({
  reducer: persistedReducer,
  devTools: process.env.NODE_ENV !== "production", // 개발자도구 확인
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }),
});

export type RootState = ReturnType<typeof store.getState>;

export default store;
export const persistor = persistStore(store);

 

whitelist 부분에 전역으로 넣을 변수를 넣어주는 곳이다. 이제 redux를 사용할 수 있게 컴포넌트로 감싸주는 provider 컴포넌트를 하나 만들어주자. 아래와 같이 만든다.

 

/redux/provider.tsx

import { ReactNode } from "react";
import { Provider } from "react-redux";
import store, { persistor } from "./store";
import { PersistGate } from "redux-persist/integration/react";

const Providers = ({ children }: { children: ReactNode }) => {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        {children}
      </PersistGate>
    </Provider>
  );
};

export default Providers;

 


 

위에서 설정은 끝났다. 사용하는 부분에 provider 컴포넌트를 넣어준다. 리액트를 사용한다면, root 디렉터리 컴포넌트에 넣어주면 되고 next.js를 사용 중이라면, _app.tsx나 layout.tsx에 넣어주자. 예시는 아래와 같다.

 

// 적용 전
import Head from "next/head";
import type { AppProps } from "next/app";
import "/styles/globals.css";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
        <Component {...pageProps} />
    </>
  )
};

 

// 적용 후
import Head from "next/head";
import type { AppProps } from "next/app";
import "/styles/globals.css";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Providers>
        <Component {...pageProps} />
      </Providers>
    </>
  );
}

 

위와 같이 Providers 컴포넌트로 설정해 준 부분에서는 Redux를 사용가능하다. 기본 구조는 다 만들었다. 사용로직을 위한 Slice를 작성해 보자.

 

 


 

대표적인 로그인 관련된 auth Slice를 예제로 작성해 보겠다. authSlice.ts를 만들자. 지금까지 파일 구조는 아래와 같다.

/redux
  /slice
    /authSlice.ts
  /provider.tsx
  /store.ts

 

authSlice의 코드는 아래와 같다.

 

import { createSlice } from "@reduxjs/toolkit";
import { RootState } from "../store";

export const initialAuthState: AuthState = {
  isLoggedIn: false,
  email: null,
  userName: null,
  userType: null,
};

const authSlice = createSlice({
  name: "auth",
  initialState: initialAuthState,
  reducers: {
    SET_ACTIVE_USER: (state, action) => {
      const { email, userName, userType } = action.payload;
      state.isLoggedIn = true;
      state.email = email;
      state.userName = userName;
      state.userType = userType;
    },
    REMOVE_ACTIVE_USER: () => initialAuthState,
  },
});

export const { SET_ACTIVE_USER, REMOVE_ACTIVE_USER } = authSlice.actions;

export const selectIsLoggedIn = (state: RootState) => state.auth.isLoggedIn;
export const selectEmail = (state: RootState) => state.auth.email;
export const selectUserName = (state: RootState) => state.auth.userName;
export const selectUserType = (state: RootState) => state.auth.userType;

export default authSlice.reducer;

 

initialAuthState는 초기값을 설정한 것이다. authSlicecreateSlice를 통해 이름, 초기값, 리듀서를 설정해 준다. SET_ACTIVE_USER를 실행하면, action으로 받은 값들을 각각의 redux에 등록을 해주는 것이고, REMOVE_ACTIVE_USER는 로그아웃 시 초기 값으로 상태값들을 변경하는 로직이다. 

 


위의 slice는 하나의 그룹으로 생각해도 된다. 만약 추가도 다른 로직이 필요하다면, 추가로 slice를 만들어주면 된다. 이제 만든 slice를 store에 등록하여 컴포넌트에서 사용할 수 있게 하자. store.ts는 아래와 같이 추가한다.

 

import { combineReducers, configureStore } from "@reduxjs/toolkit";
import storage from "redux-persist/lib/storage";

import authReducer from "./slice/authSlice";
import { persistReducer, persistStore } from "redux-persist";

const persistConfig = {
  key: "root",
  storage,
  whitelist: ["auth"],
};

const rootReducer = combineReducers({
  auth: authReducer,
});

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = configureStore({
  reducer: persistedReducer,
  devTools: process.env.NODE_ENV !== "production",
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }),
});

export type RootState = ReturnType<typeof store.getState>;

export default store;
export const persistor = persistStore(store);

 

위에서 보면 추가된 부분은 combineReducers 부분에 auth: authReducer를 넣어준 것과, localStorage에 저장을 위해 persistConfig내부의 Whitelist에는 우리가 createSlice에 넣었던 이름(auth)을 넣어주면 된다. 여기까지 하면 redux 설정이 완료된다.

 


Redux를 사용해 보자. 사용하기 위해 불러오는 부분은 slice에서 export 된 아래의 코드라고 할 수 있다.

 

export const { SET_ACTIVE_USER, REMOVE_ACTIVE_USER } = authSlice.actions;

export const selectIsLoggedIn = (state: RootState) => state.auth.isLoggedIn;
export const selectEmail = (state: RootState) => state.auth.email;
export const selectUserName = (state: RootState) => state.auth.userName;
export const selectUserType = (state: RootState) => state.auth.userType;

 

action부분은 SET_ACTIVE_USER, REMOVE_ACTIVE_USER를 통해 실행한다. 사용법은 아래와 같다.

 

import { useMutation, useQueryClient } from "@tanstack/react-query";
import { postSignIn, postSignOut } from "./api";
import { useDispatch } from "react-redux";
import { REMOVE_ACTIVE_USER, SET_ACTIVE_USER } from "@/redux/slice/authSlice";

export const useSigninQuery = (redirect: (path: string) => void) => {
  const queryClient = useQueryClient();
  const dispatch = useDispatch();

  return useMutation(postSignIn, {
    onSuccess: (response) => {
      switch (response.result) {
        case "SUCCESS":
          dispatch(
            SET_ACTIVE_USER({
              isLoggedIn: true,
              email: response.data.id,
              userName: response.data.id,
              userType: response.data.userType,
            })
          );
      }
    },
  });
};

 

위의 코드에서 확인할 부분은 useDispatch()를 통해 dispatch 변수로 만들어 준 부분과, dispatch 안에 REMOVE_ACTIVE_USER를 넣어서 사용한 부분이 끝이다. 위의 예시에서는 react-query 내부에서 dispatch가 사용되어 어려워 보일 수도 있지만, 간소화해서 다시 표현하면 아래와 같다.

 

const TestComponent = () => {
    const dispatch = useDispatch();

    const handleClick = (e: FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        dispatch(
              SET_ACTIVE_USER({
                  isLoggedIn: true,
                  email: "hanpy@hanpy.com",
                  userName: "py",
                  userType: "admin",
            })
        );
    }
    return (
        <>
            <button onClick={handleClick}>로그인 발생!!</button>
        </>
    )


}

 

 

위의 부분이 데이터를 변경하는 action 부분이었다면, 아래는 데이터를 가져올 수 있는 부분에 대한 예시이다. 사실 이 부분은 간단하게 구현할 수 있다.

 

 

import { useSelector } from "react-redux";
import { selectUserName } from "@/redux/slice/authSlice";


const GNB = () => {
  const userName = useSelector(selectUserName);
  
  ...

 

useSelector를 우선 Import 해준다. 그리고 slice부분에서 export해준 데이터 중에 필요한 부분을 가져와서 사용하면 된다. 복잡해 보이지만 사실 redux-tookit을 사용하면 어느 정도 쉽게 전역변수관리가 된다고 할 수 있다. 

 

마지막으로 데이터 변경이 없다면 useSelector를 활용하여 redux의 값들을 가지고 와서 UI에 바로 사용하면 된다. 하지만 데이터 변경이 있다면, redux상태값을 useEffect를 통해 useState 변수에 넣어서 사용하도록 하자.

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함