카테고리 없음

redux-toolkit 초기세팅

합주기 2024. 5. 23. 03:46

로그인 여부를 원하는 컴포넌트에서 사용할 수 있게 redux-toolkit을 사용하여 isLogin을 전역변수로 뺄 수 있다. 또한 매번 타입을 지정해줘야하는 불편함을 제거하기 위해 custom hook을 만들것이다.

설치하기

npm install @reduxjs/toolkit react-redux

폴더 구조

hooksstore 폴더의 구조를 살펴본다.

redux 폴더 구조

store 생성하기

store는 리덕스 저장소이며 각 리듀서를 저장하는 공간이다.
store/index.tsx 파일에 다음과 같이 작성한다.

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

const store = configureStore({
  reducer: {} // 객체 내부에는 이 후에 작성한다.
});

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

export default store;

리액트에 store을 등록

리액트에서 store를 사용할 수 있도록 src/index.tsx에 등록한다.

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

slice 생성하기

slice는 store에 저장되는 리듀서를 "기능별로 슬라이스한 것"이라고 생각하면 편하다.
로그인 슬라이스, 모달창 슬라이스 등 기능별로 분리하여 작성한다.

loginSlice.tsx 파일에 로그인 여부라는 전역 상태를 만들고 로그인, 로그아웃 시 true, false를 인자로 받아 로그인 여부를 변경시키는 함수를 만들었다.

import { PayloadAction, createSlice } from "@reduxjs/toolkit";

interface ILoginState {
  isLogin: boolean;
}
const initialState: ILoginState = {
  isLogin: false,
};
export const loginSlice = createSlice({
  name: "login",
  initialState,
  reducers: {
    setLogin: (state, { payload }: PayloadAction<boolean>) => {
      state.isLogin = payload;
    },
  },
});

export const { setLogin } = loginSlice.actions;
export default loginSlice.reducer;

 

⭐ 마지막에 setLogin 함수와 reducer를 빼먹지 않고 내보내야 한다. 현재에는 reducers에 setLogin 함수밖에 없지만 추가할 경우 {setLogin, 추가 함수} 형식으로 내보내야한다.
또한 reducer는 store에 등록할 때 사용된다.

여러개의 reducer를 하나의 reducer로 합치기

여러개의 slice가 있을 경우 여러개의 reducer가 생긴다. 지금은 하나의 loginReducer밖에 없지만 모달창, 로그 기능 슬라이스 등을 만들면 그 만큼 reducer도 늘어날 것이다.
따라서 store에 reducer을 등록하기 전 reducer/reducer.tsx 라는 별도의 파일을 생성하여 reducer을 합쳐준다.

import loginSlice from "../slices/loginSlice";

const reducer = {
  login: loginSlice,
};

export default reducer;

store에 reducer 등록하기

다시 store/index.tsx 파일로 돌아와서 reducer : {} 남겨두었던 부분을 reducer : reducer(합친 리듀서)로 작성한다.

import { configureStore } from "@reduxjs/toolkit";
import reducer from "./reducer/reducer";

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

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

export default store;

redux hook 생성하기

훅을 사용하는 이유는 보다 간편하게 useSelector, useDispatch를 사용하기 위함이다.

  • useSelector: 전역 상태를 가져온다.
  • useDispatch: 함수를 호출한다.
    - 만약 타입스크립트가 아닌 자바스크립트일 경우 useSelector 문법은 다음과 같다.
    const count = useSelector((state) => state.login.isLogin);
    - 이것을 타입스크립트로 나타내면 state의 타입을 매번 지정해줘야하는 불편함이 있다.
    const count = useSelector((state: RootState) => state.login.isLogin);

따라서 다음과 같이 훅을 만든다.

import { TypedUseSelectorHook, useSelector, useDispatch } from "react-redux";

import { RootState, AppDispatch } from "../store";

export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;

export const useTypedDispatch = () => useDispatch<AppDispatch>();

나는 타입을 미리 명시한 useTypedSelector, useTypeDispatch 훅을 만들었다.

컴포넌트에서 사용하기

카카오에 로그인 요청 시 Oauth 컴포넌트가 실행되고 인가코드를 받아 우리 서비스의 백엔드에 인가코드를 넘기고, 성공하면 로그인 여부를 true로 변경시키는 스켈레톤 코드이다.


- dispatch(setLogin(true))로 상태를 변경하면 state.login을 사용하는 모든 컴포넌트에서 재 렌더링이 된다.

import React, { useEffect } from "react";
import { useTypedDispatch, useTypedSelector } from "../hooks/redux";
import { setLogin } from "../store/slices/loginSlice";

const Oauth = () => {
  const code = new URL(document.location.toString()).searchParams.get("code");
  const login = useTypedSelector((state) => state.login);
  const dispatch = useTypedDispatch();

  useEffect(() => {
    console.log(code);
    // 백엔드에 code(인가 코드) 넘기기

    // 성공하면 isLogin
    dispatch(setLogin(true));

  }, [code, dispatch]);

  console.log("렌더링!");
  return (
    <div>
      <div>Login Status: {login.isLogin ? "Logged In" : "Not Logged In"}</div>
    </div>
  );
};

export default Oauth;

 

 

 + 추가적으로 리덕스의 재렌더링에 대해서도 공부하였다.

구독하는 상태 슬라이스에 따른 재렌더링

  1. 상태 구독('useSelector') : 컴포넌트가 'useSelector' 훅을 사용하여 특정 상태 조각을 구독하고 있는 경우, 그 상태 조각이 변경될 때만 해당 컴포넌트가 재렌더링된다.
  2. 상태 참조 분리 : 다른 상태 조각을 구독하는 컴포넌트는 영향을 받지 않음
💬 예를 들어 로그인 슬라이스와 모달창 슬라이스가 있고 로그인 슬라이스 내에 로그인 여부, 모달창 슬라이스 내에 모달창 열림 여부가 있다고 가정

- state.login.isLogin이 변경된다고 해서 state.modal.isOpen을 참조하는 컴포넌트가 렌더링되지 않음
- state.login.isLogin과 state.login.id는 같은 login 슬라이스를 참조하기 때문에 state.login을 구독하는 모든 컴포넌트가 재렌더링됨