React
를 사용해 프로그래밍을 하다 보면, 상위 Component
의 상태를 하위 Component
에 전달해야될 때가 있다.
문제는 리액트 컴포넌트가 Tree
구조로 이뤄져있어, 목표 Component
에 도달하기 위한 depth가 깊다면 필연적으로 props drilling
문제를 마주하게 된다.
//이해를 돕기위한 간단한 예시입니다.
function Header({userId}) {
return <Title userId={userId} />
}
function Title({userId}) {
return <Content userId={userId} />
}
function Content({userId}) {
return <Main userId={userId} />
}
function Main({userId})...
위의 예제 코드에서 실질적으로 userId
를 사용하는 것이 Main Component
뿐이라면, 중간에 위치한 Component
들은 userId
를 알 필요도 전달받을 필요도 없다.
실수로 인해 중간에서 userId
의 Data
가 바뀔 위험도 있고, 이렇게 문제가 발생하면 어디에서 문제가 발생했는지 일일히 찾아보아야 한다.
또, 변경사항이 생길 경우 모든 Component를 수정해야 한다.
매우 비효율적이고 위험도가 높은 일이다.
이를 해결해줄수 있는 것이 바로 Context API
와 useContext Hook
이다.
이것들이 어떻게 문제를 해결해줄수 있는지 원리를 알아보고, 하나씩 사용법을 알아보자.
공식 문서는 이렇게 설명하고 있다.
context는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된
방법입니다.
그러한 데이터로는 현재 로그인한 유저, 테마, 선호하는 언어 등이 있습니다.
context의 주된 용도는 다양한 레벨에 네스팅된 많은 컴포넌트에게 데이터를 전달하는 것입니다.
context를 사용하면 컴포넌트를 재사용하기가 어려워지므로 꼭 필요할 때만 쓰세요.
여러 레벨에 걸쳐 props 넘기는 걸 대체하는 데 context보다 컴포넌트 합성이 더 간단한 해결책일 수도 있습니다.
여기서 주목해야 하는 부분은 2곳 인데,
첫번째 전역적 데이터를 공유
두번째 재사용성이 떨어진다
예를 들어 Dark Mode
를 지원하는 앱에서 isDarkMode
라는 state
의 값이 True
일 때 Component
들의 Style
이 바뀐다고 하면 화면에 표시되는 모든 Component
는 isDarkMode
라는 state
를 알고 있어야 한다.
isDarkMode
가 True
일 때 DarkMode
에 맞는 style
로 바꿔주기 위해서다.
때문에 isDarkMode
는 전역적인 Data
로 존재해야 하고 Context
를 사용하기에 알맞은 Data
이다.
두번째 재사용성이 떨어진다 는 부분은 Context API
를 사용하기 위해 필요한 Provider
에 대한 이해가 필요하다.
자세한 사용법은 뒤에서 다루고 간단한 예시를 들어 설명하겠다.
// timecontext.js
// 값을 할당해주지 않았다.
export const TimeContext = createContext(null);
// App.js
function App() {
const [time,setTime] = useState(1);
return (
// value를 통해 값을 할당해주었다.
<TimeContext.provider value={{time,setTime}}>
<Header />
<TimeContext.provider />
)
}
// header.js
function Header () {
// useContext를 통해 DATA를 가져온다.
const { time, setTime } = useContext(TimeContext);
// setTime함수를 이용해 state값 수정
const handleTime = () => {
setTime(time + 1)
};
//state 값 사용
return (
<button onClick={handleTime}>
{time}
</button>
)
}
위의 코드는 추후에 다른 곳에서 재활용하기 어렵다.
provider
를 통해 감싸져 있어야하고, context
의 Data
가 이전에 사용된 것과 일치하지 않으면 동작하지 않을 것이기 때문이다.
공식 문서의 내용중 가장 중요하다고 생각하는 2가지를 간단하게 설명해보았다.
이 뒤로는 간단하게 Context API
에 대한 설명과 useContext Hook
사용법을 다루어 보도록 하겠다.
일반적인 React 애플리케이션에서 데이터는 위에서 아래로 (즉, 부모로부터 자식에게) props를 통해 전달되지만, 애플리케이션 안의 여러 컴포넌트들에 전해줘야 하는 props의 경우 (예를 들면 선호 로케일, UI 테마) 이 과정이 번거로울 수 있습니다. context를 이용하면, 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 이러한 값을 공유하도록 할 수 있습니다.
context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있습니다.
React Context 공식문서
언제나 API
에 대한 설명은 그 API
를 만든 사람들의 글을 읽는 것이 가장 좋다!
Context API
는 전역적으로 필요한 Data
를 중첩이 많이 된 Component Tree
에서 보다 쉽게 전달하기 위한 API
이다.
위의 공식문서에 들어가 좀 더 자세한 설명을 읽어보면 좋을 것 같다.
이제 사용법을 알아보기 위해 위의 예제 코드를 가져와 자세히 살펴보자.
먼저 Context
를 만들기 위해 createContext
를 Import
해준다.
그 다음 TimeContext
에 새로운 Context
를 만들어 할당해준다.
이 때 createContext
의 Parameter
로 들어가는 값을 defaultValue
라고 부르는데, 공식문서에서는
defaultValue 매개변수는 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 값입니다. 이 기본값은 컴포넌트를 독립적으로 테스트할 때 유용한 값입니다.
Provider를 통해 undefined을 값으로 보낸다고 해도 구독 컴포넌트들이 defaultValue 를 읽지는 않는다는 점에 유의하세요.
라고 설명되어 있다.
본 예제는 null
을 defaultValue
로 설정했는데, 적절한 값을 찾지 못하는 오류등이 발생했을 경우 확실하게 파악하기 위함이다.
이어서 설명하자면, 생성된 context
를 export
하여 내보낸다.
// context.js
// createContext를 import한다.
import { createContext } from 'react';
// 새로운 context를 만들어 TimeContext 변수에 할당해준다.
export const TimeContext = createContext(null);
//export를 통해 내보내준다.
그리고 최상위 컴포넌트 파일에서 context
를 import
하여 가져온다.
component
의 가장 바깥쪽에 아까 만든 context
의 이름.provider
를 통해 component
들을 감싸준다.
provider
는 하위 Component
들이 자신의 상위에 존재하는 provider
를 구독(쉽게 말해 그 값을 사용)할 수 있게 해준다.
(styled-component
등을 사용해 보았다면 익숙할 것이다. ThemeProvider
도 비슷한 원리로 동작하는 것 같다.)
이 때 provider
는 value
라는 속성을 가지는데 이 value
가 context
의 Data
가 된다.
value
로는 어떤 값이든 올 수 있다.
ex) 함수, 객체, 배열, 문자열, 숫자 ...
여기서는 time
이라는 state
와, 그것을 관리하는 setTime
을 한꺼번에 할당하기 위해 중괄호로 감싸주었다.
// App.js
//import하여 가져옴
import { TimeContext } from './context';
function App() {
const [time,setTime] = useState(1);
return (
//context.provider를 통해 component들을 감싸준다.
//이로써 TimeContext.provider 하위에 위치하는 component들은 TimeCotext를 구독하게 된다.
<TimeContext.provider value={{time,setTime}}>// 값을 할당해 주었다.
<Header />
<TimeContext.provider />
)
}
이제 context
를 사용하여 component
를 완성해보자.
먼저 TimeContext
를 사용하기 위해 import
해온다.
그 다음 useContext
를 사용하기 위해 마찬가지로 import
해온다.
이제 TimeContext
의 값을 사용하기 위해 useContext
를 사용해 값을 가져온다.
구조분해할당
을 이용해 time
과 setTime
을 한번에 할당해주었다.
이후 편하게 사용하면 된다.
// import
import { TimeContext } from './context';
import { useContext } from 'react';
function Header () {
// useContext를 통해 DATA를 가져온다.
const { time, setTime } = useContext(TimeContext); // 값을 할당해준다.
const handleTime = () => {
setTime(time + 1)// setTime함수를 이용해 state값 수정
};
return (
<button onClick={handleTime}>
{time}//state 값 사용
</button>
)
}
여기까지 useContext에 대한 기초적인 설명을 해보았다.
학습한 내용을 정리하며 작성한 글인데 작성하면서 나 자신도 이전보다 더 잘 이해하게 된 것 같아 뿌듯하다.
본문에 따로 작성하지 않았지만, provider
의 value prop
이 바뀔때 마다 Context
를 구독하고 있는 모든 Component
가 다시 렌더링된다.
이를 최적화하는데 도움이 되는 글을 아래에 남기며 글을 마친다.