| title | useContext |
|---|
useContext 는 컴포넌트에서 context를 읽고 구독할 수 있는 React Hook입니다.
const value = useContext(SomeContext)useContext 를 컴포넌트의 최상위 수준에서 호출하여 context를 읽고 구독합니다.
import { useContext } from 'react';
function MyComponent() {
const theme = useContext(ThemeContext);
// ...SomeContext:createContext로 생성한 context입니다. context 자체는 정보를 담고 있지 않으며, 컴포넌트에서 제공하거나 읽을 수 있는 정보의 종류를 나타냅니다.
useContext 는 호출하는 컴포넌트에 대한 context 값을 반환합니다. 이 값은 트리에서 호출하는 컴포넌트 위의 가장 가까운 SomeContext.Provider 에 전달된 value로 결정됩니다. provider가 없으면 반환된 값은 해당 컨텍스트에 대해 createContext 에 전달한 defaultValue 가 됩니다. 반환된 값은 항상 최신 상태입니다. 컨텍스트가 변경되면 React는 자동으로 해당 context 를 읽는 컴포넌트를 다시 렌더링합니다.
- 컴포넌트 내의
useContext()호출은 동일한 컴포넌트에서 반환된 provider에 영향을 받지 않습니다. 해당하는<Context.Provider>는useContext()호출을 하는 컴포넌트 위에 배치되어야 합니다. - React는 다른
value을 받는 provider로부터 시작해서 특정 context를 사용하는 모든 자식들을 자동으로 리렌더링합니다. 이전 값과 다음 값은Object.is비교를 통해 비교됩니다.memo로 리렌더링을 건너뛰어도 자식들이 새로운 context 값을 받는 것을 막지는 못합니다. - 빌드 시스템이 결과물에 중복 모듈을 생성하는 경우(심볼릭 링크에서 발생할 수 있음) context가 손상될 수 있습니다. context를 통해 무언가를 전달하는 것은
===비교에 의해 결정되는 것처럼 컨텍스트를 제공하는 데 사용하는SomeContext와 context를 읽는 데 사용하는SomeContext가 정확하게 동일한 객체인 경우에만 작동합니다.
컴포넌트의 최상위 수준에서 useContext를 호출하여 context를 읽고 구독합니다.
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
// ... useContext는 전달한 context 에 대한 context value 를 반환합니다. context 값을 결정하기 위해 React는 컴포넌트 트리를 검색하고 특정 context에 대해 상위에서 가장 가까운 context provider를 찾습니다.
context를 Button에 전달하려면 해당 버튼 또는 상위 컴포넌트 중 하나를 해당 context provider로 감쌉니다:
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... 내부에서 버튼을 렌더링합니다. ...
}provider와 Button사이에 얼마나 많은 컴포넌트 레이어가 있는지는 중요하지 않습니다. Form 내부의 Button 이 어디에서나 useContext(ThemeContext)를 호출하면,"dark"를 값으로 받습니다.
useContext() 는 항상 호출하는 컴포넌트 상위에서 가장 가까운 provider를 찾습니다. 위쪽으로 찾고 useContext()를 호출하는 컴포넌트 안의 provider는 고려하지 않습니다.
import { createContext, useContext } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
)
}
function Form() {
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>
);
}.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}가끔은 context가 시간이 지남에 따라 변경되기를 원할 것입니다. context를 업데이트 하려면 state와 결합하십시오. 부모 컴포넌트에서 state변수를 선언하고 현재 state를context value로 provider에 전달합니다.
function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Form />
<Button onClick={() => {
setTheme('light');
}}>
Switch to light theme
</Button>
</ThemeContext.Provider>
);
}이제 provider 내부의 모든 Button은 현재 theme 값을 받게 됩니다. provider에게 전달된 theme값을 업데이트 하기 위해 setTheme을 호출하면, 모든Button 컴포넌트가 새로운 'light' 값으로 리렌더링 됩니다.
이 예시에서 MyApp 컴포넌트는 state 변수를 가지고 있고, 이 state 변수는 ThemeContext provider 로 전달됩니다. "Dark mode" 체크박스를 체크하면 state가 업데이트 됩니다. 제공된 값을 변경하면 해당 context를 사용하는 모든 컴포넌트가 리렌더링 됩니다.
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>
);
}.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}value="dark"는 "dark" 문자열을 전달하지만, value={theme}는 JSX 중괄호를 사용하여 JavaScript theme 변수 값을 전달합니다. 중괄호를 사용하면 문자열이 아닌 context 값도 전달할 수 있습니다.
이 예시에서는 object를 가지고 있는 currentUser 상태 변수가 있습니다. { currentUser, setCurrentUser }를 하나의 객체로 결합하여 value={} 내부의 컨텍스트를 통해 전달합니다. 이렇게 하면 LoginButton과 같은 아래의 모든 컴포넌트가 currentUser와 setCurrentUser를 모두 읽은 다음 필요할 때 setCurrentUser를 호출할 수 있습니다.
import { createContext, useContext, useState } from 'react';
const CurrentUserContext = createContext(null);
export default function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
return (
<CurrentUserContext.Provider
value={{
currentUser,
setCurrentUser
}}
>
<Form />
</CurrentUserContext.Provider>
);
}
function Form({ children }) {
return (
<Panel title="Welcome">
<LoginButton />
</Panel>
);
}
function LoginButton() {
const {
currentUser,
setCurrentUser
} = useContext(CurrentUserContext);
if (currentUser !== null) {
return <p>You logged in as {currentUser.name}.</p>;
}
return (
<Button onClick={() => {
setCurrentUser({ name: 'Advika' })
}}>Log in as Advika</Button>
);
}
function Panel({ title, children }) {
return (
<section className="panel">
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ children, onClick }) {
return (
<button className="button" onClick={onClick}>
{children}
</button>
);
}label {
display: block;
}
.panel {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.button {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}이 예시에서는 두 개의 독립적인 context가 있습니다. ThemeContext는 문자열인 현재 테마를 제공하고 CurrentUserContext는 현재 사용자를 나타내는 object를 보유합니다.
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
const CurrentUserContext = createContext(null);
export default function MyApp() {
const [theme, setTheme] = useState('light');
const [currentUser, setCurrentUser] = useState(null);
return (
<ThemeContext.Provider value={theme}>
<CurrentUserContext.Provider
value={{
currentUser,
setCurrentUser
}}
>
<WelcomePanel />
<label>
<input
type="checkbox"
checked={theme === 'dark'}
onChange={(e) => {
setTheme(e.target.checked ? 'dark' : 'light')
}}
/>
Use dark mode
</label>
</CurrentUserContext.Provider>
</ThemeContext.Provider>
)
}
function WelcomePanel({ children }) {
const {currentUser} = useContext(CurrentUserContext);
return (
<Panel title="Welcome">
{currentUser !== null ?
<Greeting /> :
<LoginForm />
}
</Panel>
);
}
function Greeting() {
const {currentUser} = useContext(CurrentUserContext);
return (
<p>You logged in as {currentUser.name}.</p>
)
}
function LoginForm() {
const {setCurrentUser} = useContext(CurrentUserContext);
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const canLogin = firstName.trim() !== '' && lastName.trim() !== '';
return (
<>
<label>
First name{': '}
<input
required
value={firstName}
onChange={e => setFirstName(e.target.value)}
/>
</label>
<label>
Last name{': '}
<input
required
value={lastName}
onChange={e => setLastName(e.target.value)}
/>
</label>
<Button
disabled={!canLogin}
onClick={() => {
setCurrentUser({
name: firstName + ' ' + lastName
});
}}
>
Log in
</Button>
{!canLogin && <i>Fill in both fields.</i>}
</>
);
}
function Panel({ title, children }) {
const theme = useContext(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ children, disabled, onClick }) {
const theme = useContext(ThemeContext);
const className = 'button-' + theme;
return (
<button
className={className}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}label {
display: block;
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}앱이 성장함에 따라 앱의 루트에 더 가까운 context의 "피라미드"를 갖게 될 것입니다. 이는 잘못된 것이 아닙니다. 하지만 중첩이 보기에 좋지 않다면 provider들을 단일 컴포넌트로 분리할 수 있습니다. 이 예제에서 MyProviders는 "context들"을 숨기고 필요한 provider들의 내부에 전달된 자식을 렌더링합니다. theme 및 setTheme 상태는 MyApp 자체에 필요하므로 MyApp이 여전히 해당 상태를 소유하고 있습니다.
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
const CurrentUserContext = createContext(null);
export default function MyApp() {
const [theme, setTheme] = useState('light');
return (
<MyProviders theme={theme} setTheme={setTheme}>
<WelcomePanel />
<label>
<input
type="checkbox"
checked={theme === 'dark'}
onChange={(e) => {
setTheme(e.target.checked ? 'dark' : 'light')
}}
/>
Use dark mode
</label>
</MyProviders>
);
}
function MyProviders({ children, theme, setTheme }) {
const [currentUser, setCurrentUser] = useState(null);
return (
<ThemeContext.Provider value={theme}>
<CurrentUserContext.Provider
value={{
currentUser,
setCurrentUser
}}
>
{children}
</CurrentUserContext.Provider>
</ThemeContext.Provider>
);
}
function WelcomePanel({ children }) {
const {currentUser} = useContext(CurrentUserContext);
return (
<Panel title="Welcome">
{currentUser !== null ?
<Greeting /> :
<LoginForm />
}
</Panel>
);
}
function Greeting() {
const {currentUser} = useContext(CurrentUserContext);
return (
<p>You logged in as {currentUser.name}.</p>
)
}
function LoginForm() {
const {setCurrentUser} = useContext(CurrentUserContext);
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const canLogin = firstName !== '' && lastName !== '';
return (
<>
<label>
First name{': '}
<input
required
value={firstName}
onChange={e => setFirstName(e.target.value)}
/>
</label>
<label>
Last name{': '}
<input
required
value={lastName}
onChange={e => setLastName(e.target.value)}
/>
</label>
<Button
disabled={!canLogin}
onClick={() => {
setCurrentUser({
name: firstName + ' ' + lastName
});
}}
>
Log in
</Button>
{!canLogin && <i>Fill in both fields.</i>}
</>
);
}
function Panel({ title, children }) {
const theme = useContext(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ children, disabled, onClick }) {
const theme = useContext(ThemeContext);
const className = 'button-' + theme;
return (
<button
className={className}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}label {
display: block;
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}규모가 큰 앱에서는 컨텍스트와 reducer를 결합하여 컴포넌트에서 특정 상태와 관련된 로직을 분리하는 것이 일반적입니다. 이 예제에서는 모든 "wiring"이 reducer와 두 개의 개별 contexts가 포함된 TasksContext.js에 숨겨져 있습니다.
이 예제에 대한 전체 안내를 읽어보세요.
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';
export default function TaskApp() {
return (
<TasksProvider>
<h1>Day off in Kyoto</h1>
<AddTask />
<TaskList />
</TasksProvider>
);
}import { createContext, useContext, useReducer } from 'react';
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
}
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
const initialTasks = [
{ id: 0, text: 'Philosopher’s Path', done: true },
{ id: 1, text: 'Visit the temple', done: false },
{ id: 2, text: 'Drink matcha', done: false }
];import { useState, useContext } from 'react';
import { useTasksDispatch } from './TasksContext.js';
export default function AddTask() {
const [text, setText] = useState('');
const dispatch = useTasksDispatch();
return (
<>
<input
placeholder="Add task"
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={() => {
setText('');
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}}>Add</button>
</>
);
}
let nextId = 3;import { useState, useContext } from 'react';
import { useTasks, useTasksDispatch } from './TasksContext.js';
export default function TaskList() {
const tasks = useTasks();
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
<Task task={task} />
</li>
))}
</ul>
);
}
function Task({ task }) {
const [isEditing, setIsEditing] = useState(false);
const dispatch = useTasksDispatch();
let taskContent;
if (isEditing) {
taskContent = (
<>
<input
value={task.text}
onChange={e => {
dispatch({
type: 'changed',
task: {
...task,
text: e.target.value
}
});
}} />
<button onClick={() => setIsEditing(false)}>
Save
</button>
</>
);
} else {
taskContent = (
<>
{task.text}
<button onClick={() => setIsEditing(true)}>
Edit
</button>
</>
);
}
return (
<label>
<input
type="checkbox"
checked={task.done}
onChange={e => {
dispatch({
type: 'changed',
task: {
...task,
done: e.target.checked
}
});
}}
/>
{taskContent}
<button onClick={() => {
dispatch({
type: 'deleted',
id: task.id
});
}}>
Delete
</button>
</label>
);
}button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }React가 부모 트리에서 특정 context providers를 찾을 수 없는 경우, useContext()가 반환하는 context 값은 해당 context를 생성할 때지정한 기본값과 동일합니다.
const ThemeContext = createContext(null);기본값은 변경되지 않습니다. context를 업데이트하려면 위에서 설명한 대로 상태와 함께 사용하세요.
예를 들어 null 대신에 기본값으로 사용할 수 있는 더 의미 있는 값이 있는 경우가 많습니다.
const ThemeContext = createContext('light');이렇게 하면 실수로 해당 provider 없이 일부 컴포넌트를 렌더링해도 깨지지 않습니다. 또한 테스트 환경에서 많은 provider를 설정하지 않고도 컴포넌트가 테스트 환경에서 잘 작동하는 데 도움이 됩니다.
아래 예제에서 "Toggle theme" 버튼은 테마 context provider의 외부 에 있고 기본 컨텍스트 테마 값이 'light'이기 때문에 항상 밝게 표시되어 있습니다. 기본 테마를 'dark'로 변경해 보세요.
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext('light');
export default function MyApp() {
const [theme, setTheme] = useState('light');
return (
<>
<ThemeContext.Provider value={theme}>
<Form />
</ThemeContext.Provider>
<Button onClick={() => {
setTheme(theme === 'dark' ? 'light' : 'dark');
}}>
Toggle theme
</Button>
</>
)
}
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, onClick }) {
const theme = useContext(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className} onClick={onClick}>
{children}
</button>
);
}.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}트리의 일부분을 다른 값의 provider로 감싸서 해당 부분에 대한 context를 오버라이드 할 수 있습니다.
<ThemeContext.Provider value="dark">
...
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
...
</ThemeContext.Provider>필요한 만큼 provider를 중첩하고 오버라이드 할 수 있습니다.
여기서 Footer 내부의 버튼은 외부의 버튼("dark")과 다른 context 값("light")을 받습니다.
import { createContext, useContext } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
)
}
function Form() {
return (
<Panel title="Welcome">
<Button>Sign up</Button>
<Button>Log in</Button>
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
</Panel>
);
}
function Footer() {
return (
<footer>
<Button>Settings</Button>
</footer>
);
}
function Panel({ title, children }) {
const theme = useContext(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
{title && <h1>{title}</h1>}
{children}
</section>
)
}
function Button({ children }) {
const theme = useContext(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className}>
{children}
</button>
);
}footer {
margin-top: 20px;
border-top: 1px solid #aaa;
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}context provider를 중첩할 때 정보를 "누적"할 수 있습니다. 이 예시에서 Section 컴포넌트는 섹션 중첩의 깊이를 지정하는 LevelContext를 추적합니다. 이 컴포넌트는 부모 섹션에서 LevelContext를 읽은 다음 1씩 증가한 LevelContext 숫자를 자식에게 제공합니다. 그 결과 Heading 컴포넌트는 얼마나 많은 Section 컴포넌트가 중첩되어 있는지에 따라 <h1>, <h2>, <h3>, ..., 태그 중 어떤 태그를 사용할지 자동으로 결정할 수 있습니다.
이 예제에 대한 자세한 안내를 읽어보세요.
import Heading from './Heading.js';
import Section from './Section.js';
export default function Page() {
return (
<Section>
<Heading>Title</Heading>
<Section>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Section>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Section>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
</Section>
</Section>
</Section>
);
}import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Section({ children }) {
const level = useContext(LevelContext);
return (
<section className="section">
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
const level = useContext(LevelContext);
switch (level) {
case 0:
throw Error('Heading must be inside a Section!');
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
case 5:
return <h5>{children}</h5>;
case 6:
return <h6>{children}</h6>;
default:
throw Error('Unknown level: ' + level);
}
}import { createContext } from 'react';
export const LevelContext = createContext(0);.section {
padding: 10px;
margin: 5px;
border-radius: 5px;
border: 1px solid #aaa;
}context를 통해 객체와 함수를 포함한 모든 값을 전달할 수 있습니다.
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>
);
}여기서 context value은 두 개의 프로퍼티를 가진 자바스크립트 객체이며, 그 중 하나는 함수입니다. MyApp이 다시 렌더링할 때마다(예를 들어 경로 업데이트 시) 다른 함수를 가리키는 다른 객체가 될 것이므로 React는 useContext(AuthContext)를 호출하는 트리 깊숙한 곳에 있는 모든 컴포넌트도 다시 렌더링해야 합니다.
작은 앱에서는 문제가 되지 않습니다. 그러나 currentUser와 같은 기본적인 데이터가 변경되지 않았다면 다시 렌더링할 필요가 없습니다. React가 이 사실을 활용할 수 있도록 login 함수를 useCallback으로 감싸고 객체 생성을 useMemo로 감싸면 됩니다. 이것이 성능 최적화입니다.
import { useCallback, useMemo } from 'react';
function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
const login = useCallback((response) => {
storeCredentials(response.credentials);
setCurrentUser(response.user);
}, []);
const contextValue = useMemo(() => ({
currentUser,
login
}), [currentUser, login]);
return (
<AuthContext.Provider value={contextValue}>
<Page />
</AuthContext.Provider>
);
}이 변경으로 인해 MyApp이 다시 렌더링해야 하는 경우에도 currentUser가 변경되지 않는 한 useContext(AuthContext)를 호출하는 컴포넌트는 다시 렌더링할 필요가 없습니다.
useMemo 와 useCallback 에 대해 자세히 알아보세요.
이런 일이 발생하는 몇 가지 이유가 있습니다.
useContext()를 호출하는 컴포넌트와 동일한 컴포넌트(또는 그 아래)에서<SomeContext.Provider>를 렌더링하는 경우.<SomeContext.Provider>를useContext()를 호출하는 컴포넌트의 위와 바깥으로 이동하세요.- 컴포넌트를
<SomeContext.Provider>로 감싸는 것을 잊었거나 생각했던 것과 다른 트리의 다른 부분에 배치했을 수 있습니다. React 개발자 도구를 사용하여 계층 구조가 올바른지 확인하세요. - 제공하는 컴포넌트에서 보는
someContext와 읽는 컴포넌트에서 보는someContext가 서로 다른 두 개의 객체가 되는 빌드 문제가 발생할 수 있습니다. 예를 들어 심볼릭 링크를 사용하는 경우 이런 문제가 발생할 수 있습니다. 이를 확인하려면window.SomeContext1와window.SomeContext2을 전역에 할당하고 콘솔에서window.SomeContext1 === window.SomeContext2인지 확인하면 됩니다. 동일하지 않은 경우 빌드 도구 수준에서 해당 문제를 수정하세요.
기본값이 다른데도 context가 undefined이 됩니다. {/i-am-always-getting-undefined-from-my-context-although-the-default-value-is-different/}
트리에 value가 없는 provider가 있을 수 있습니다.
// 🚩 Doesn't work: no value prop
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>value를 지정하는 것을 잊어버린 경우, value={undefined}를 전달하는 것과 같습니다.
실수로 다른 prop의 이름을 실수로 사용했을 수도 있습니다.
// 🚩 Doesn't work: prop should be called "value"
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>두 가지 경우 모두 콘솔에 React에서 경고가 표시될 것입니다. 이를 수정하려면 prop value를 호출하세요.
// ✅ Passing the value prop
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>createContext(defaultValue) 호출의 기본값은 위에 일치하는 provider가 전혀 없는 경우에만 사용된다는 점에 유의하세요. 부모 트리 어딘가에 <SomeContext.Provider value={undefined}> 컴포넌트가 있는 경우, useContext(SomeContext)를 호출하는 컴포넌트는 undefined를 context 값으로 받습니다.