useContext 컴포넌트에서 Context를 읽고 subscribe할 수 있는 React Hook
<목차>
ContextuseContextProps는 부모 컴포넌트에서 자식 컴포넌트로 정보를 전달할 때 사용되지만, 여러 컴포넌트를 거치게 될 경우 복잡해질 수 있다. 이럴 때 Context를 사용하면 명시적으로 props를 전달하지 않아도 트리 구조의 하위 컴포넌트들이 해당 props(공통 데이터)에 직접 접근할 수 있다.

위 Context에 대한 설명과 같은 prop drilling에 대한 얘기임
→ props는 깊어질 수 있고 이를 위한 '순간이동'기능이 Context.

Context를 사용하기 전에 다음을 고려하라고 한다.
- 우선은
props를 통해 전달하는 방식부터 시작하세요.
컴포넌트가 단순하지 않다면 여러 개의 props를 여러 단계의 컴포넌트를 거쳐 전달하는 일은 흔하다.
이 과정이 번거롭게 느껴질 수 있지만 어떤 컴포넌트가 어떤 데이터를 사용하는지 명확하게 보여주기 때문에 유지보수하는 사람 입장에서는 오히려 고마운 일이다.
props를 통해 데이터 흐름을 명시적으로 표현하는 것도 좋은 방법이다.
- 중간 컴포넌트가 props를 안 쓰는데 계속 넘겨주는 상황이면
children구조로 바꿔서 데이터 전달 경로를 줄이면 된다.<Layout> <Posts posts={posts} /> </Layout>이 이외의 상황이라면 Context를 고려해도 좋다
const value = useContext(SomeContext)
Parameters
someContext: createContext로 생성한 context 자체는 실제 데이터를 가지고 있는 것이 아니라 어떤 종류의 데이터를 컴포넌트들이 주고받을 수 있는지를 표현하는 역할만 한다Returns
useContext는 호출한 컴포넌트에서 사용할 context의 값을 반환한다.
트리 상에서 가장 가까운 <SomeContext.Provider>에 전달된 value에 따라 결정
이 값은 컴포넌트 된다.
만약 트리 상위에 그런 Provider가 없다면, createContext 할 때 지정했던 defaultValue가 반환된다.
또한 반환된 값은 항상 최신 상태이다.
React는 해당 context 값이 변경되면 이를 참조하는 컴포넌트를 자동으로 리렌더링한다.
// Provider 제공 X
const MyContext = useContext("defaultValue")
// Provider 제공 O
<MyContext.Provider value="offerByProvider">
<App/>
</MyContext.Provider>
Caveats

useContext()를 호출할 경우 그 컴포넌트 안에서 반환된 Provider에는 영향을 받지 않는다.useContext()를 사용하는 컴포넌트 위쪽에 위치한 <Context.Provider>여야만 정상적으로 context 값을 제공한다. function Example() {
const value = useContext(MyContext); // 여기선 아직 Provider가 적용 안 됨
return (
<MyContext.Provider value="값">
{/* ... */}
</MyContext.Provider>
);
}
// Provider는 트리 상에서 위에 있어야 한다.
React는 특정 context 값을 사용하는 모든 하위 컴포넌트를 자동으로 다시 렌더링한다.
이때, context의 이전 값과 새 값은 Object.is를 기준으로 비교된다.
React.memo는 context로 인한 리렌더링을 막을 수 없다. → 리렌더링
빌드 시스템이 (심볼릭 링크 등으로 인해) 중복된 모듈을 만들어내는 경우, context가 정상적으로 동작하지 않을 수 있다.
context는 제공할 때 사용하는 SomeContext와 읽을 때 사용하는 SomeContext가 === 비교로 완전히 동일한 객체일 때만 정상 작동한다.
아래는 공식문서에서 제공한 useContext에 대한 예제 코드
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<Form />
<label>
<input
type="checkbox"
checked={theme === 'dark'}
onChange={(e) => {
setTheme(e.target.checked ? 'dark' : 'light')
}}
/>
Use dark mode
</label>
</ThemeContext.Provider>
)
}
function Form({ children }) {
return (
<Panel title="Welcome">
<Button>Sign up</Button>
<Button>Log in</Button>
</Panel>
);
}
function Panel({ title, children }) {
const theme = useContext(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ children }) {
const theme = useContext(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className}>
{children}
</button>
);
}
function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
function login(response) {
storeCredentials(response.credentials);
setCurrentUser(response.user);
}
return (
<AuthContext.Provider value={{ currentUser, login }}>
<Page />
</AuthContext.Provider>
);
}
useContext()로 객체나 함수가 들어있는 값을 전달할 때
매번 새로운 객체가 생기면 → 자식 컴포넌트가 불필요하게 리렌더링된다.
이를 방지하기 위해 useCallback,useMemo를 활용해서 자식 컴포넌트의 리렌더링을 최적화할 수 있다.
function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
const login = useCallback((response) => {
setCurrentUser(response.user);
}, []);
const contextValue = useMemo(() => ({
currentUser,
login,
}), [currentUser, login]);
return (
<AuthContext.Provider value={contextValue}>
<Page />
</AuthContext.Provider>
);
}
MyApp이 리렌더링 되더라도 useContext(AuthContext)를 사용하는 하위 컴포넌트들은
currentUser가 변경되지 않는 한 리렌더링 X