React에서 상위 컴포넌트의 데이터를 하위 컴포넌트로 전달할 때,
중간에 있는 여러 컴포넌트들을 거쳐가야 하는 현상.
데이터가 여러 층을 '뚫고(drilling)' 내려가는 것과 같다고 해서 붙여진 이름이다.
최상위 컴포넌트에서 가장 하위 컴포넌트로 사용자 정보를 전달하려면, 그 사이에 있는 모든 컴포넌트들이 props를 받아서 다시 전달해야 하는 불편함이 생기고 코드의 가독성과 유지보수성을 저하시킨다.
React의 Context API로 중간 컴포넌트들을 거치지 않고도 직접 데이터를 전달 가능.
간단한 전역 상태 관리에 적합하지만,
너무 많은 Context를 사용하면 컴포넌트 재사용이 어려워질 수 있다.
Redux나 Recoil과 같은 상태 관리 라이브러리를 사용하면 전역 상태 효과적 관리 가능.
강력한 상태 관리 기능을 제공하지만,
작은 애플리케이션에서는 과도한 설정이 필요할 수 있다.
Redux 예제
🖥️ // userSlice.js
import { createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: {
name: 'John Doe',
email: 'john@example.com'
},
reducers: {
updateUser: (state, action) => {
return { ...state, ...action.payload };
}
}
});
export const { updateUser } = userSlice.actions;
export default userSlice.reducer;
// store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
export const store = configureStore({
reducer: {
user: userReducer
}
});
// App.js
import { Provider } from 'react-redux';
import { store } from './store';
function App() {
return (
<Provider store={store}>
<MainLayout />
</Provider>
);
}
// UserProfile.js
import { useSelector, useDispatch } from 'react-redux';
import { updateUser } from './userSlice';
function UserProfile() {
const user = useSelector(state => state.user);
const dispatch = useDispatch();
const handleUpdateName = () => {
dispatch(updateUser({ name: 'Jane Doe' }));
};
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={handleUpdateName}>
Change Name
</button>
</div>
);
}
4. 컴포넌트 구조 개선
컴포넌트 구조 재설계도 Props Drilling을 피하는 방안이 될 수 있다.
가장 기본적인 해결책이지만, 항상 가능한 것은 아니라는 단점이 있다.
Context API는 React에서 제공하는 데이터 전달 방식으로,
"전역적인 데이터 공유 환경"을 만들어주는 기능이다.
마치 컴포넌트 트리 전체를 감싸는 하나의 데이터 우산을 만드는 것과 같다.
1. Provider(제공자)
Context의 데이터를 제공하는 컴포넌트.
보통 애플리케이션의 최상위나 데이터가 필요한 컴포넌트 트리의 최상위에 위치한다.
value prop을 통해 공유할 데이터를 지정한다.
2. Consumer(소비자)
Context의 데이터를 사용하는 컴포넌트.
useContext 훅을 사용하여 데이터에 접근한다.
Provider가 제공하는 value를 받아서 사용할 수 있다.
예를 들어, 다크 모드/라이트 모드 테마 설정을 구현할때,
🖥️ // UserContext.js
import React, { createContext, useContext, useState } from 'react';
const UserContext = createContext();
export function UserProvider({ children }) {
const [user, setUser] = useState({
name: 'John Doe',
email: 'john@example.com'
});
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
export function useUser() {
return useContext(UserContext);
}
// App.js
function App() {
return (
<UserProvider>
<MainLayout />
</UserProvider>
);
}
// MainLayout.js
function MainLayout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// Sidebar.js
function Sidebar() {
return (
<div>
<Navigation />
</div>
);
}
// Navigation.js
function Navigation() {
return (
<div>
<UserProfile />
</div>
);
}
// UserProfile.js
function UserProfile() {
const { user, setUser } = useUser();
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={() => setUser({ ...user, name: 'Jane Doe' })}>
Change Name
</button>
</div>
);
}