prop-drilling을 useContext와 rtk(Redux Toolkit)로 전환하기

최종욱·2025년 2월 10일

react

목록 보기
4/7

배경

React 애플리케이션에서는 상위 컴포넌트의 데이터를 여러 중간 컴포넌트를 거쳐서 최종적으로 하위 컴포넌트에 전달해야 할 때, 매 단계마다 props로 데이터를 넘겨주는 prop-drilling 문제가 발생할 수 있다. 이 방식은 컴포넌트 구조가 깊어질수록 관리가 어렵고, 불필요한 props 전달로 인해 코드가 지저분해질뿐만 아니라, 리랜더링 과정이 불필요하게 일어나기 때문에 상당히 안좋다.

이번 포스트에서는 prop-drilling의 문제를 간단한 예제로 보여주고, 이를 useContext를 활용해 보다 깔끔하게 바꾸는 법을 쓰려고 한다.

1. Prop-Drilling 예제

먼저, 간단한 사용자 정보를 전달하는 예제를 통해 알아보자. 여기서는 Parent 컴포넌트에서 사용자 객체를 생성한 후, Child와 GrandChild 컴포넌트를 거쳐 최종적으로 GrandChild에서 해당 정보를 사용하는 예제이다.

// Parent.jsx
import React from "react";
import Child from "./Child";

const Parent = () => {
  const user = { name: "John Doe", age: 30 };

  return (
    <div>
      <h1>Parent Component</h1>
      <Child user={user} />
    </div>
  );
};

export default Parent;
// Child.jsx
import React from "react";
import GrandChild from "./GrandChild";

const Child = ({ user }) => {
  return (
    <div>
      <h2>Child Component</h2>
      <GrandChild user={user} />
    </div>
  );
};

export default Child;
// GrandChild.jsx
import React from "react";

const GrandChild = ({ user }) => {
  return (
    <div>
      <h3>GrandChild Component</h3>
      <p>User Name: {user.name}</p>
      <p>User Age: {user.age}</p>
    </div>
  );
};

export default GrandChild;

Parent에서 생성한 user 객체를 Child를 통해 GrandChild에 계속 전달해야 한다. 만약 사용자 정보가 여러 컴포넌트에서 필요하거나, 컴포넌트 계층이 더 깊어진다면 매번 props로 넘겨줘야 하므로 관리가 복잡해진다.

2. useContext를 활용한 해결 방법

useContext를 사용하면, 별도의 중간 컴포넌트에 props를 전달할 필요 없이 전역 상태처럼 데이터를 사용할 수 있다. 위 예제를 useContext를 활용해서 바꿔보자.

2.1. Context 생성
먼저, 사용자 정보를 담을 Context를 생성(파일을 따로)

// UserContext.js
import React, { createContext, useState } from "react";

export const UserContext = createContext();

export const UserProvider = ({ children }) => {
  const [user] = useState({ name: "John Doe", age: 30 });

  return (
    <UserContext.Provider value={user}>{children}</UserContext.Provider>
  );
};

여기서는 간단하게 user 객체만 전역에서 관리하도록 했지만, 상태 업데이트 함수도 함께 제공가능

2.2. Provider로 앱 최상단 감싸기
UserProvider로 최상위 컴포넌트를 감싸서, 하위 컴포넌트들이 Context에 접근할 수 있도록 한다.

// App.jsx
import React from "react";
import Parent from "./Parent";
import { UserProvider } from "./UserContext";

function App() {
  return (
    <UserProvider>
      <Parent />
    </UserProvider>
  );
}

export default App;

이렇게 하면, Parent를 포함한 모든 하위 컴포넌트는 user 데이터를 Context를 통해 접근 가능

2.3. 중간 컴포넌트에서는 더 이상 props 전달이 필요 없음
이제 Child와 GrandChild에서 더 이상 props로 user를 전달할 필요가 없음

// Child.jsx (리팩토링 전)
import React from "react";
import GrandChild from "./GrandChild";

const Child = ({ user }) => {
  return (
    <div>
      <h2>Child Component</h2>
      <GrandChild user={user} />
    </div>
  );
};

export default Child;
// Child.jsx (리팩토링 후)
import React from "react";
import GrandChild from "./GrandChild";

const Child = () => {
  return (
    <div>
      <h2>Child Component</h2>
      <GrandChild />
    </div>
  );
};

export default Child;

2.4. 최종 하위 컴포넌트에서 Context 사용
GrandChild 컴포넌트에서 useContext 훅을 사용해 user 데이터를 가져오기

// GrandChild.jsx (리팩토링 후)
import React, { useContext } from "react";
import { UserContext } from "./UserContext";

const GrandChild = () => {
  const user = useContext(UserContext);

  return (
    <div>
      <h3>GrandChild Component</h3>
      <p>User Name: {user.name}</p>
      <p>User Age: {user.age}</p>
    </div>
  );
};

export default GrandChild;

이제 GrandChild는 직접 Context에서 user 객체를 읽어오므로, 중간 컴포넌트로부터 props를 전달받을 필요가 없음




3.RTK를 활용한 해결 방법

  1. 이 : 이벤트 발생
  2. 디 : 디스패치 호출
  3. 액 : 디스패치는 인자로 액션 객체를 받음
  4. 타 : 액션의 타입을 정의함
  5. 페 : 액션의 페이로드를 정의함
  6. 써 : 리듀서는 액션의 타입과 페이로드를 받아 상태를 업데이트함
  7. 스 : 스토어는 리듀서를 통해 상태를 업데이트(state가 변경되어 컴포넌트가 리랜더링)

1. Redux Toolkit 설치 및 기본 설정
먼저 RTK와 React-Redux를 설치

yarn add @reduxjs/toolkit react-redux

2. Slice 생성
RTK의 createSlice를 사용하여 user 상태를 관리할 slice를 만듭니다.

// userSlice.js
import { createSlice } from "@reduxjs/toolkit";

const initialState = { 
  user: { name: "John Doe", age: 30 } 
};

const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    // 만약 사용자 정보를 업데이트하는 액션이 필요하다면 여기 추가 가능
    updateUser(state, action) {
      state.user = action.payload;
    },
  },
});

export const { updateUser } = userSlice.actions;
export default userSlice.reducer;

3. Store 생성

// store.js
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";

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

export default store;

4.Provider로 앱 최상단 감싸기

// App.jsx
import React from "react";
import { Provider } from "react-redux";
import store from "./store";
import Parent from "./Parent";

function App() {
  return (
    <Provider store={store}>
      <Parent />
    </Provider>
  );
}

export default App;
  1. 중간 컴포넌트 수정(Prop Drilling 제거)
이제 Child 컴포넌트는 더 이상 user props를 전달할 필요가 없음.
// Child.jsx (리팩토링 후)
import React from "react";
import GrandChild from "./GrandChild";

const Child = () => {
  return (
    <div>
      <h2>Child Component</h2>
      <GrandChild />
    </div>
  );
};

export default Child;

6. 최종 하위 컴포넌트에서 Redux state 사용

// GrandChild.jsx (리팩토링 후)
import React from "react";
import { useSelector } from "react-redux";

const GrandChild = () => {
  const user = useSelector((state) => state.user.user);

  return (
    <div>
      <h3>GrandChild Component</h3>
      <p>User Name: {user.name}</p>
      <p>User Age: {user.age}</p>
    </div>
  );
};

export default GrandChild;

정리

useContext 리팩토링

  1. Context 생성
    전역 데이터 관리를 위한 Context와 Provider(UserContext)를 만들기

  2. Provider 감싸기
    최상위 컴포넌트(App 등)에서 Provider로 전체 컴포넌트 트리를 감싸기

  3. 중간 컴포넌트 정리
    더 이상 props를 전달하지 않도록 중간 컴포넌트에서 관련 코드를 제거

  4. 하위 컴포넌트 사용
    최종 하위 컴포넌트에서 useContext로 전역 데이터를 직접 읽어오기

RTK 리팩토링

  1. Slice 생성
    RTK의 createSlice로 전역 상태(user)를 관리할 slice를 만든다.
  2. Store 생성
    configureStore를 사용해 Redux store를 구성한다.
  3. Provider 감싸기
    App.jsx에서 Provider로 전체 앱을 감싼다.
  4. 중간 컴포넌트 정리
    더 이상 prop-drilling을 하지 않도록 중간 컴포넌트를 수정한다.
  5. useSelector 사용
    최종 하위 컴포넌트에서 useSelector를 사용하여 전역 상태를 직접 읽어온다.



비교

비교 항목useContextRedux Toolkit (RTK)
설정 및 간편함React 내장 API로 간단하게 설정 가능추가 라이브러리 설치 필요, 초기 설정 및 학습 곡선 있음
적용 대상소규모 또는 간단한 전역 상태 관리에 적합복잡하고 대규모 상태 관리에 유리함
성능상태 변경 시 Provider 하위 모든 컴포넌트가 리렌더링될 수 있음중앙 집중식 상태 관리와 미들웨어, DevTools 지원으로 성능 관리 용이
디버깅 및 도구별도의 디버깅 도구가 없음Redux DevTools 등 강력한 디버깅 도구 제공
코드 구조단순한 데이터 전달에 적합, 코드 구조가 간단함상태, 액션, 리듀서 등 체계적으로 관리하여 유지보수에 유리함
profile
항상 “Why?”로 시작하는 프론트엔드 개발자

0개의 댓글