useContext
는 컴포넌트가 컨텍스트를 읽고 구독할 수 있도록 하는 리액트 훅이에요.
const value = useContext(SomeContext);
Reference
useContext(SomeContext)
컨텍스트를 읽거나 구독하기 위해서는 최상위 컴포넌트에서 useContext
를 호출하세요.
import { useContext } from 'react';
function MyComponent(){
const theme = useContext(ThemeContext);
// ...
}
Parameters | 파라미터(매개면수)
SomeContext
:createContext
로 미리 생성한 컨텍스트. 컨텍스트 자체만으로는 정보를 가질 수 없으며 컴포넌트를 통해 제공하거나 읽을 수 있는 종류의 정보만을 나타내요.
Returns | 반환값
useContext
는 컴포넌트를 호출하기 위한 컨텍스트 값을 반환해요. 이 값은 컴포넌트 트리에서 현재 useContext
를 호출한 컴포넌트 상위의 컴포넌트 중 가장 가까운 SomeContext.Provider
에서 전달받은 값으로 정해져요. 만약 이 조건을 만족하는 provider가 없다면 컨텍스트에 createContext
로 전달한 defaultValue
가 반환값이 돌 거예요. 반환값은 항상 최신 값이에요. 컨텍스트가 수정되면 리액트는 해당 컨텍스트를 읽는 컴포넌트를 자동적으로 리렌더링하기 때문이에요.
Caveat | 주의사항
- 컴포넌트에서 호출한
useContext()
는 같은 컴포넌트로부터 반환받은 provider들에 영향을 받지 않아요. 해당하는<Context.Provider>
는useContext()
를 호출하는 컴포넌트보다 상위에 위치해야해요. - 리액트는 다른 값을 받은 provider에서 시작하는 특정 컨텍스트를 사용하는 모든 자식을 자동적으로 리렌더링해요. 이전 값과 다음 값은
Object.js
를 사용하여 비교돼요.memo
로 리렌더링을 건너뛰는 것은 자식 컴포넌트가 새로운 컨텍스트 값을 받는 것을 막지 않아요. - 만약 빌드 시스템이 (심볼릭 링크로 발생할 수 있는) 결과물에 중복 모듈을 생산한다면 오류가 발생할 수 있어요. 컨텍스트를 통해 무언가를 전달하는 것은 컨텍스트를 제공하기 위해 사용한
SomeContext
와 컨텍스트를 읽기 위해 사용한SomeContext
가===
비교를 통하여 결정했을 때, 정확히 같은 객체일 때만 동작해요
Usage | 용법
Passing data deeply into the tree | 데이터를 트리에 깊이 전달하기
컨텍스트를 읽고 구독하는 컴포넌트의 최 상위 컴포너트에서 useContext
를 호출하세요.
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
// ...
}
useContext
는 전달받은 컨텍스트에서 컨텍스트 값을 반환해요. 컨텍스틑 값을 정하기 위해서 리액트는 컴포넌트 트리를 탐색하고 해당 컴포넌트보다 상위에 있는 컨텍스트 중 가장 가까운 컨텍스트를 찾아요.
Button
에 컨텍스트를 전달하려면 해당 컴포넌트나 그 부모 컨포넌트를 해당하는 컨텍스트 provider로 감싸세요.
function MyPage(){
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... renders buttons inside ...
}
provider와 Button의 컴포넌트의 계층 차이는 중요하지 않아요. Form 컴포넌트 하위에 있는 Button이 useContext(ThemeContext)
을 호출하면, 값으로 "dark"를 받을 거예요.
함정
useContext()는 항상 호출된 컴포넌트의 상위 provider 중 가장 가까운 provider를 찾아요. 이 때 상향식으로 탐색하고 `useContext()`를 호출한 컴포넌트 내부에 있는 provider는 탐색하지 않아요.
Updating data passed via context | 컨텍스트를 통해 전달된 데이터 업데이트 하기
종종 시간에 따라 수정해야하는 컨텍스트가 필요해요. 컨텍스트를 업데이트하려면 [상태](https://react.dev/reference/react/useState)와 함께 사용하세요. 부모 컴포넌트에서 상태 변수를 선언하고 provider에 컨텍스트 값으로 현재 상태를 전달하세요.
function MyPage(){
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value="dark">
<Form />
<Button onClick={() => {
setTheme('light');
}}>
라이트 모드로 변경하기
</Button>
</ThemeContext.Provider>
);
}
이제 provider 안의 모든 Button
은 현재 theme
값을 받아요. 만약 provider로 전달하는 theme
의 값을 변경하기 위해서 setTheme
을 호출한다면 모든 Button
컴포넌트는 새로운 'light'
라는 값으로 리렌더링 될 거예요.
컨텍스트 업데이트 예시
1. 컨텍스트를 통해 값 업데이트 하기 (코드 보기)
이 예시에서 MyApp
컴포넌트는 ThemeContext
의 provider로 전달될 상태 변수를 갖고 있어요. "다크 모드(Dark mode)" 체크박스를 체킹하면 상태가 변경돼요. 제공받은 값을 변경하는 것은 컨텍스트를 사용하는 모든 컴포넌트를 리렌더링해요.
2. 컨텍스트를 통해 객체 업데이트 하기 (코드보기)
이 예시에는 객체를 갖고 있는 currentUser
상태변수가 있어요. 단일 객체로 { currentUser, serCurrentUser }
를 합치고 value={}
내부에서 컨텍스트를 통해 해당 객체를 전달하세요. 이렇게 전달하면 LoginButton
과 같은 하위 컴포넌트는 currentUser
와 setCurrentUser
를 모두 읽고 setCurrentUser
를 필요할 때 호출할 수 있어요.
3. 다중 컨텍스트 (코드 보기)
이 예시에는 두 개의 독립 컨텍스트가 있어요. ThemeContext
는 문자열(string)로 현재 테마를 제공하는 반면 CurrentUserContext
는 현재 사용자를 나타내는 객체를 갖고 있어요.
4. 컴포넌트로 provider 추출하기 (코드 보기)
어플리케이션이 성장하면 앱의 루트에 가까운 위치의 컨텍스트 "피라미드"가 생길 것으로 예상돼요. 여기에는 문제가 없어요. 하지만 만약 중첩된 모양이 싫다면 provider를 단일 컴포넌트로 추출할 수 있어요. 예를 들어, MyProviders
는 "배관"을 숨기고 필요한 provider 내에서 전달된 자식 컴포넌트를 렌더링해요. theme
과 setTheme
상태는 MyApp
자체에서 필요로 하기 때문에 MyApp
은 여전히 해당 상태를 가지고 있다는 점을 명심하세요.
5. 컨텍스트와 리듀셔로 확장하기 (코드 보기)
더 큰 어플리케이션에서는 컴포넌트 외부에 있는 상태와 연관된 로직을 추출하기 위해 리듀서로 컨텍스트를 합치는 것이 일반적이에요. 이번 예시에서 모든 "배선"은 리듀서와 두 개의 분리된 컨텍스트를 포함하는 TaskContext.js
에 숨겨져요.
이 예시의 전체적인 과정을 참고하세요.
Specifying a fallback default value | fallback 기본 값 지정하기
만약 리액트가 부모 트리에서 특정한 컨텍스트의 provider를 찾을 수 없다면 useContext()
에서 반환된 컨텍스트 값은 컨텍스트를 만들 때 정한 기본값과 같아요:
const ThemeContext = createContext(null);
기본값은 절대 바뀌지 않아요. 만약 컨텍스트를 변경하고 싶다면 위의 파트에 기술된 대로 상태와 함께 사용하세요.
보통은 null
대신 기본값으로 사용할 수 있는 더 의미있는 값이 있어요. 예시를 보여드릴게요.
const ThemeContext = createContext('light');
이렇게 하면 실수로 해당하는 provider 없이 컴포넌트를 렌더링 하더라도 오류가 발생하지 않아요. 또한 테스트 환경에서 많은 provider를 설정하지 않아도 컴포넌트가 잘 동작하는데 도움이 돼요.
아래의 예시처럼, 테마 컨텍스트 provider의 바깥에 있고 컨텍스트의 기본 테마 값은 'light'
이기 때문에 "테마 변경하기" 버튼은 항상 light 테마에요. 기본 값이'dark'
가 되도록 수정해보세요.
Overriding context for a part of the tree | 일부 트리의 컨텍스트 재정의(오버라이딩)하기
다른 값을 가진 provider로 특정 부분을 감싼다면 일부 트리의 컨텍스트를 재정의 할 수 있어요.
<ThemeContext.Provider value="dark">
...
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
...
</ThemeContext.Provider>
필요한만큼 중첩하고 재정의할 수 있어요.
컨텍스트 재정의 예시
1. 테마 재정의 (코드 보기)Footer
안에 있는 버튼은 바깥에 있는 버튼의 컨텍스트 값("dark"
)과는 다른 컨텍스트 값("light"
)을 받아요.
2. 자동적으로 중첩된 제목 (코드 보기)
컨텍스트 provider를 중첩하면 정보를 축적할 수 있어요. 이번 예시에서 Section
컴포넌트는 섹션 중천의 깊이를 정하는 LevelContext
를 추적해요. Section
컴포넌트는 부모 섹션에서 LevelContext
를 읽고 그 값을 하나 늘려서 자식 컴포넌트에 전달해요. 결론적으로 Heading
컴포넌트는 내부에 중첩된 Section
컴포넌트의 개수에 따라 자동적으로 <h1>
, <h2>
, <h3>
, ... , 태그로 결정할 수 있어요.
이 예시의 자세한 과정을 읽어보세요.
Optimizing re-renders when passing objects and functions | 객체과 함수를 전달할 때 리렌더링 최적화하기
컨텍스트를 통해서 객체와 함수를 포함한 모든 값을 넘길 수 있어요.
function MyApp(){
const [currentUser, setCurrentUser] = useState(null);
function login(response) {
storeCredentials(response.credential);
setCurrentUser(response.user);
}
return (
<AuthContext.Provider value={{currentUser, login}}>
<Page />
</AuthContext.Provider>
)
}
여기서 컨텍스트 값은 두 개의 속성을 가진 자바스크립트 객체로 그 중 하나는 함수예요. MyApp
이 리렌더링 될 때(예를 들면, 라우트 업데이트 시)마다 context value는 다른 함수를 가리키는 다른 객체가 되고 리액트도 useContext(AuthContext)
를 호출하는 트리 안의 모든 컴포넌트를 리렌더링 해요.
규모가 작은 어플리케이션에서는 문제가 되지 않아요. 하지만 currentUser
와 같은 기본 데이터가 변경되지 않는다면 컴포넌트들을 리렌더링 할 필요가 없어요. 리액트가 이 사실을 활용하도록 만들기 위해서는 login
함수를 useCallback
으로 감싸고 객체를 생성하는 부분도 useMemo
로 감싸주세요. 다음은 성능 최적화의 예시예요:
function MyApp(){
const [currentUser, setCurrentUser] = useState(null);
const login = userCallback((response) => {
storeCredentials(response.credential);
setCurrentUser(response.user);
}, []);
const contextValue = useMemo(() => {
currentUser,
login
}), [currentUser, login]);
return (
<AuthContext.Provider value={contextValue}>
<Page />
</AuthContext.Provider>
);
}
결론적으로 MyApp
이 리렌더링을 필요로 하더라도 useContext(AuthContext)
를 호출하는 컴포넌트는 currentUser
가 변경되지 않는 이상 리렌더링 될 필요가 없어요.
useMemo
와 useCallback
에 대해 더 알아보세요.
Troubleshooting | 트러블슈팅
My Component doesn't see the value from my provider | 컴포넌트에서 provider의 값이 보이지 않아요
위와 같은 상황이 발생하는 몇 가지 일반적인 이유가 있어요:
<SomeContext.Provider>
를useContext()
를 호출하는 컴포넌트와 같은 (또는 해당 컴포넌트보다 하위의) 컴포넌트에서 렌더링했어요.<SomeContext.Provider>
를useContext()
를 호출한 컴포넌트의 상위 혹은 바깥으로 옮기세요.<SomeContext.Provider>
로 컴포넌트를 감싸는 것을 잊어버렸거나 생각한 것과 다른 부분을 넣었을지도 몰라요. React DevTools를 활용하여 계층이 올바른지 확인하세요.- 제공하는 컴포넌트에서 보는
SomeContext
와 읽는 컴포넌트에서 보는SomeContext
가 두 개의 다른 객체로 보이는 빌드 문제가 발생했을지도 몰라요. 이 문제는 예를들어서 심볼릭 링크를 사용한다면 발생할 수 있어요.window.SomeContext1
과window.SomeContext2
와 같이 전역변수에 할당하고window.SomeContext1 === window.SomeContext2
가 맞는지를 콘솔을 통해 체크해서 검사할 수 있어요. 만약 이 두 컨텍스트가 다르다면 빌드 레벨에서 이 이슈를 해결하세요.
I am always getting undefined
from my context although the default value is different | 기본값은 다름에도 불구하고 컨텍스트에서 항상 undefined
를 가져와요.
트리에서 value
없는 provider를 가지고 있을 거예요.
// 🚩 동작 불가 : value prop이 없어요.
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>
만약 value
를 지정하는 것을 잊어버렸다면 value = {undefined}
를 전달한 것처럼 동작해요.
또한 실수로 다른 prop 이름을 사용했을 수도 있어요.
// 🚩 동작 불가 : prop은 "value"로 호출되어야해요
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>
위의 두 예시에서 당신은 콘솔창에는 리액트에서 띄운 warning 문구를 볼 거예요. 이 문제를 해결하려면 value
로 prop을 호출하세요.
// ✅ value prop 전달하기
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>
createContext(defaultValue)
의 기본 값을 호출하는 것은 상위에 해당하는 provider가 없을 때에만 사용된다는 사실을 명심하세요. <someContext.Provider value={undefined}>
컴포넌트가 부모 트리의 어딘가에 있다면 useContext(SomeContext)
를 호출하는 컴포넌트는 컨텍스트 값으로 undefined
를 받을 거예요.
'리액트 공식문서 | React Docs > Reference > react@18.2.0' 카테고리의 다른 글
[Hooks] useDeferredValue | useDeferredValue 훅 (1) | 2024.01.14 |
---|---|
[Hooks] useDebugValue | useDebugValue 훅 (0) | 2024.01.11 |
[Hooks] useCallback | useCallback훅 (2) | 2024.01.04 |
[Hooks] use | use 훅 (1) | 2023.12.28 |
[Hooks] Hooks | 리액트 내장 훅 (0) | 2023.12.22 |