Next13 버전 이상에 Redux-toolkit 적용하기

수정·2024년 1월 24일

Next

목록 보기
4/4
post-thumbnail

혼자서 Next.js로 간단한 게임을 하나 만드는 중이다.
게임을 만들면서 전역 상태 관리가 필요한데, redux-toolkit을 Next.js에 처음 적용하다보니 잘 되지 않는 게 있었다.

구글링을 하면서 Next.js에서의 적용법을 찾아냈고,
오늘은 적용하는 법에 대해 포스팅해보려고 한다.

참고: Next.js Redux-toolkit 적용 미디엄 게시글

정확한 정보 수정을 위한 피드백은 언제나 환영입니다✨


Redux-toolkit 왜 평범하게 적용되지 않았을까?

원래는 리액트에서 사용했던 것처럼 react-redux 패키지가 제공하는 Provider로 하위 컴포넌트를 감싸 사용하려고 했다.
Next.js에서도 똑같이 하면 되겠지라 생각했지만, 내 착각이었다😅

기본적으로 Next 13 이상 버전은 컴포넌트가 기본적으로 서버 컴포넌트로 제공된다.

redux-toolkit의 내부적인 액션 처리는 window 객체와 같은 CSR의 개념을 가지고 있다.
그러나 서버 컴포넌트 같이 서버에서 동작하는 파일의 경우 당연히 window같은 클라이언트 전용 객체가 존재하지 않는다.
그 환경에서 redux-toolkit이 일반적으로 동작하지 못했고, 때문에 에러가 나왔던 것이다!

📍 핵심은 전역 상태를 사용하는 코드 파일(블록)은 클라이언트 컴포넌트로 변경하면 되는 것!

아 물론 createSlice로 생성한 리듀서는 클라이언트뿐만 아니라 서버 측에서도 사용이 가능하다곤 한다.

그렇다면 해결법을 알아보자

custom provider 파일 만들기

클라이언트 컴포넌트 처리를 한 custom provider를 만들어 layout 컴포넌트에 감싸주면 조금 더 깔끔한 코드를 작성할 수 있다.
나는 해당 파일을 따로 폴더 처리를 하지 않고 최상단 layout 파일과 같은 곳에 생성했다.
따로 모아 관리하고 싶거나, 본인의 폴더 생성 규칙이 있다면 그걸 참고하는 것도 좋을 것 같다.

[providers.tsx]

'use client';

import { Provider } from 'react-redux';
import store from './_store/index';

export default function Providers({ children }: { children: React.ReactNode }) {
	return <Provider store={store}>{children}</Provider>
}

[최상단 layout.tsx]

import Providers from './provider'; // custom Provider 불러오기

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'Boom Game',
  description: 'Next로 만들어보는 지뢰게임',
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <Providers>
        <body className={inter.className}>{children}</body>
      </Providers>
    </html>
  );
}

전역 상태를 읽어와 사용하는 곳은 클라이언트 컴포넌트 처리를 하자!

기본 설정까지 마쳤다면 전역 상태를 사용하는 파일로 한 번 가보도록 하자.
나는 최상단 page.tsx에서 한 버튼을 누르면 main 페이지로 이동되는 구조의 프로젝트를 개발하고 있다.

Provider를 통해 언제 어디서 어떤 파일이든 redux store에 접근할 수 있도록 했다.
나는 이 Main 페이지 파일에서 전역 상태를 읽어들여 사용할 필요가 있었다.

특히 useSelector의 경우 주로 클라이언트 측에서 사용되면서, 클라이언트 측에서 스토어를 읽어들인다.
이렇게 직접적으로 전역 상태를 읽어들이는 것과 같이 상태 관리를 하는 경우 해당 코드 파일(블록)은 클라이언트 컴포넌트 변환이 필요하다.

'use client'; // 클라이언트 컴포넌트 처리

import { useSelector } from 'react-redux';
import DifficultyButton from '../_components/difficultyButton';
import styles from './main.module.css';

function MainPage() {
  const board = useSelector((state: GameBoardType) => state.gameBoard.board);

  return (
    <main className={styles.mainWrap}>
		/* 생략 */
    </main>
  );
}

export default MainPage;

클라이언트 컴포넌트 처리 - 반드시 필요한 과정일까?

참고로 전역 상태를 사용하는 파일이더라도 use client가 꼭 필요하지 않은 경우도 있는 것 같다.

왜냐하면 내가 현재 만들고 있는 파일 중 하나는 useDispatch를 사용하여 난이도 변경이 가능하도록 하고 있다.
전역 상태 관리와 관련된 useDispatch를 쓰고 있음에도 불구하고, 따로 에러가 나지 않고 있다.

import { useDispatch } from 'react-redux';
import { setChangeOptions } from '@/app/_store/reducers/gameBoardReducer';
import styles from './difficultyButton.module.css';

function DifficultyButton() {
  const dispatch = useDispatch();

  const handleChangeDifficulty = (value: string) => {
    dispatch(setChangeOptions(value));
  };

  return (
    <ul className={styles.buttonListStyle}>
      <li>
        <button
          type="button"
          className={styles.buttonStyle}
          onClick={() => handleChangeDifficulty('beginner')}
        >
          초급 🐥
        </button>
      </li>
      <li>
        <button
          type="button"
          className={styles.buttonStyle}
          onClick={() => handleChangeDifficulty('intermediate')}
        >
          중급 🐻
        </button>
      </li>
      <li>
        <button
          type="button"
          className={styles.buttonStyle}
          onClick={() => handleChangeDifficulty('expert')}
        >
          고급 🦁
        </button>
      </li>
    </ul>
  );
}

export default DifficultyButton;

그 이유와 관련하여 기본 이론을 이용해 나름의 근거를 생각해봤다.

  • 이미 렌더링을 마친 서버에서 전달된 컴포넌트이기 때문이 아닐까?

난이도 조절 UI는, 위에서 use Client 처리를 한 메인페이지가 자식 컴포넌트로 가지고 있다.
메인페이지같은 부모 컴포넌트에서 클라이언트 컴포넌트 처리를 했고, 모두 렌더링되어 화면에 보여지고 있기 때문에 난이도 조절 상태 관리에는 영향이 가지 않는 것으로 보인다.

이처럼 useSelector, useDispatch 이런식으로 전역 상태 API를 사용한다고 해서 무조건적으로 'use Client'를 사용하지 않고,
트리 구조를 이해하며 위에서 클라이언트 컴포넌트 처리가 이미 되어있는 것 같다면 굳이 작성하지 않아도 괜찮을 것 같다고 생각한다!

profile
💛

1개의 댓글

comment-user-thumbnail
2024년 8월 19일

안녕하세요! 저도 항상 기존의 ReactJS에서 사용하던 상태 관리 라이브러리들을 nextJS에서 활용하는 것에 관심이 많은 사람입니다.
말씀해주신 것처럼 부모 컴포넌트가 'use client' 다이렉티브를 통해 클라이언트 컴포넌트로 전환되었으면, 하위의 자식 컴포넌트는 상태를 읽고 변경하는 훅들을 사용하더라도 다이렉티브를 사용하지 않아도 되는 것이 맞나요??

답글 달기