들어가며
요즘 프로젝트를 진행하면서 React Hooks에 대한 코드들을 보고 있는데, 그 중 useContext에 대해 간단히 알아보려고 합니다. React Hooks의 기본인 useState, useEffect에 대한 부분은 Jinah 선임님께서 작성해주신 React Hooks란 무엇일까? 글을 참고해주시면 좋을 것 같습니다.
useContext
일반적인 Props의 전달은 부모(최상위) > 자식(하위)으로 위에서 아래로 흐르는데 화면이 적은 프로젝트라면 (물론 상황에 따라 화면이 적어도 불편할 수 있겠지요) 그나마 괜찮겠지만 복잡하고 많은 화면을 가진 구조일수록 최상위에서 해당 Props와 관련 없는 컴포넌트들을 거쳐 전달되는 경우가 발생하게 됩니다. 이때 전역 변수를 선언해 필요한 곳에 사용하듯 useContext를 사용하면 컴포넌트 트리 안에서 해당 값이 필요한 컴포넌트에서만 불러다 사용할 수 있습니다.
예시를 통해 살펴볼까요?
Hooks에서 제공하는 예시를 토대로 조금 변형하여 작성해보았습니다.
기본 형태 코드
import React, { useState } from 'react';
const themes = {
light: {
padding: '10px',
color: '#000000',
backgroundColor: '#eeeeee',
},
dark: {
padding: '10px',
color: '#ffffff',
backgroundColor: '#222222',
},
};
function App() {
const [changeTheme, setChangeTheme] = useState(false);
return (
<Toolbar themes={themes} changeTheme={changeTheme} setChangeTheme={setChangeTheme} />
);
}
// props 로 받을 수도 있고
function Toolbar(props) {
return (
<div>
<ThemedButton
themes={props.themes}
changeTheme={props.changeTheme}
setChangeTheme={props.setChangeTheme}
/>
</div>
);
}
// { themes, changeTheme, setChangeTheme } 형태로 받을 수도 있어요.
const ThemedButton = ({ themes, changeTheme, setChangeTheme }) => {
const handleTheme = () => {
setChangeTheme((prev) => !prev);
};
return (
<button style={changeTheme ? themes.light : themes.dark} onClick={handleTheme}>
I am styled by theme context! {changeTheme ? 'themes.light' : 'themes.dark'}
</button>
);
};
export default App;
위와 같이 최상위에서 ThemedButton 컴포넌트로 내려줄 때 Toolbar 컴포넌트는 굳이 필요 없는 props를 버튼에 내려주기 위해 받아오는 것을 확인하실 수 있습니다.
예시 코드는 확인이 용이하도록 간단한 구조로 이루어져 확인이 가능하지만 많은 컴포넌트가 연결된 상태였다면 추후 추가/변경 시 찾는데 오랜 시간을 투자해야 할지도 모릅니다.
그럼 useContext를 사용하여 위의 코드를 정리해볼까요?
useContext 사용 코드
import React, { useState, createContext, useContext } from 'react';
const themes = {
light: {
padding: '10px',
color: '#000000',
backgroundColor: '#eeeeee',
},
dark: {
padding: '10px',
color: '#ffffff',
backgroundColor: '#222222',
},
};
export const ThemeContext = createContext(null);
function App() {
const [changeTheme, setChangeTheme] = useState(false);
return (
<ThemeContext.Provider value={{ themes, changeTheme, setChangeTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const { themes, changeTheme, setChangeTheme } = useContext(ThemeContext);
const handleTheme = () => {
setChangeTheme((prev) => !prev);
};
return (
<button style={changeTheme ? themes.light : themes.dark} onClick={handleTheme}>
I am styled by theme context! {changeTheme ? 'themes.light' : 'themes.dark'}
</button>
);
}
export default App;
createContext로 ThemeContext를 생성하여 상위에서 Provider로 감싸줍니다. Provider에서 props는 value를 통해 넘겨주고 하위 컴포넌트 중 해당 props를 필요로 하는 컴포넌트만 useContext를 통해 사용하면 됩니다.
Toolbar 컴포넌트의 차이가 보이시나요? 불필요한 props를 내려받아 전달해주지 않아도 되니 코드가 간결해졌고, 코드 관리 차원에서는 해당 props를 받아 사용하는 컴포넌트를 확인하기도 용이해졌습니다.
다수의 컴포넌트가 이렇게 적용된다면 훨씬 정돈된 코드를 확인할 수 있게 됩니다.
단, Provider value 값 변경 시 해당 Context를 사용하는 컴포넌트들도 같이 렌더링이 발생하여 무조건적으로 사용하게 된다면 이 부분을 방지하기 위해 Provider Wrapping 지옥에 빠지거나 오히려 컴포넌트 재사용이 어려워질 수 있으니 이 또한 용도에 맞게 사용해야 합니다.
번외로 요즘 타입스크립트로 진행하는 부분들이 있어 변형 코드도 추가해보았습니다.
typescript 적용 코드
import React, { useState, createContext, useContext } from 'react';
interface ThemeStyle {
padding: string;
color: string;
backgroundColor: string;
}
interface ThemeType {
light: ThemeStyle;
dark: ThemeStyle;
}
const themes: ThemeType = {
light: {
padding: '10px',
color: '#000000',
backgroundColor: '#eeeeee',
},
dark: {
padding: '10px',
color: '#ffffff',
backgroundColor: '#222222',
},
};
export type Props = {
changeTheme: boolean;
setChangeTheme: React.Dispatch<React.SetStateAction<boolean>>;
themes: {
light: ThemeStyle;
dark: ThemeStyle;
};
};
export const ThemeContext = createContext<Props | null>(null);
function App() {
const [changeTheme, setChangeTheme] = useState(false);
return (
<ThemeContext.Provider value={{ themes, changeTheme, setChangeTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const { themes, changeTheme, setChangeTheme } = useContext(ThemeContext) as Props;
const handleTheme = () => {
setChangeTheme((prev: any) => !prev);
};
return (
<button style={changeTheme ? themes.light : themes.dark} onClick={handleTheme}>
I am styled by theme context!!! {changeTheme ? 'themes.light' : 'themes.dark'}
</button>
);
}
export default App;
마치며
이렇게 useContext에 대해 살펴보았습니다.
useContext 사용 및 이해함에 있어 조금이나마 도움이 되셨으면 좋겠습니다. 수정이 필요한 곳 발견 시 연락 주세요. 📢
부족한 글이지만 읽어주셔서 감사합니다. 😊
그럼 안녕히…👋 -The End-