Context
context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.
형제끼리의 상태를 알려면 공통된 부모에게 상태를 올려주고 그것을 props로 서로 바라보게 할 수 있다. 이 과정에서 수많은 자식 컴포넌트들이 있어서 길어진다면 제일 밑에 있는 자식이 부모의 상태를 알려면 그 부모의 자식에게, 또 자식에게 이런 식으로 props를 내려줘야 알 수 있다. props를 내리는 과정에서 비효율적인 코드가 되고 상태가 변할 수 있고 변하는 지점을 알아차리지 못할 수도 있다. context는 props를 넘기지 않고도 부모의 상태를 변경할 수 있게 해준다.
Context를 사용하기 전에 알아두어야할 점.
일부 props를 여러 수준 깊이 전달해야 한다고 해서 해당 정보를 context에 넣어야 하는 것은 아니다. context를 사용하기 전에 고려해야 할 몇 가지 대안에 대해 살펴보자.
- props를 전달하는 것으로 시작하자. 컴포넌트가 복잡한 경우 수십 개의 컴포넌트를 통해 12개의 props를 전달하는 것은 흔하다. 힘들게 느껴질 수 있지만 어떤 컴포넌트가 어떤 데이터를 사용하는지 명확하게 해 준다.
- 컴포넌트를 추출하고 JSX를 자식처럼 전달한다. 예를 들어, 데이터 props을 직접 사용하지 않는 디자인적, 시각적인 역할을 하는 컴포넌트에게 props을 전달하려 한다. posts를 자식에게 props로 전달해준다고 할 때 <Layout posts={posts} /> 이런 형태로 직접 내려줄 것인데, 대신 레이아웃에서 자식을 props으로 데려가게 한다. <Layout><Posts posts={posts} /></Layout> 이렇게 하면 데이터를 지정하는 컴포넌트와 데이터를 필요로 하는 컴포넌트 사이의 계층 수가 줄어든다. 이러한 접근 방식 중 어느 것도 효과가 없다면 context를 고려해보자.
사용법
데이터를 트리에 깊이 전달
컴포넌트 취상위 수준에서 useContext를 호출한다.
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
// ...
useContext는 전달된 context(ThemeContext) context 값을 반환한다. context 값을 결정하기 위해 React는 컴포넌트 트리를 검색하고 특정 context에 대해 위에서 가장 가까운 context provider를 찾는다. 위 코드처럼 context를 버튼에 전달하려면 해당 context를 해당 context provider로 래핑 한다.
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... renders buttons inside ...
}
provider와 버튼 사이에 몇개의 컴포넌트가 있는지는 중요하지 않다. Form 내부 어디서든 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>
);
}
Context를 통해 전달된 데이터 업데이트
context를 업데이트하려면 State와 결합해야 한다. 상위 컴포넌트에서 상태 변수를 선언하고 현재 상태를 컨텍스트 값(value={theme})으로 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 내부의 아무 버튼들은 현재 테마 값을 받게된다. provider에게 전달받은 theme 값을 업데이트하기 위해 setTheme을 호출하면, 모든 Button 컴포넌트들은 'light' 값을 가진 채로 리렌더될 것이다.
See the Pen Untitled by KIM-JEONG-MIN (@gyflsakfn) on CodePen.
위 링크를 통하여 컨텍스트 객체 업데이트, 여러 컨텍스트, provider를 컴포넌트화 시키기, Reducer와 결합하는 예시를 확인할 수 있다.
객체 및 함수를 값으로 전달할 때 최적화
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>
);
}
위 코드에서 컨텍스트 값 { currentUser, login }은 객체이며 그중 하나는 함수이다. 리렌더 할 때마다 다른 함수를 가리 키는 다른 객체가 되므로 React는 useContext(AuthContext)를 호출하는 트리 깊은 곳에서 모든 컴포넌트를 다시 렌더링해야 한다. 이 문제를 최적화하기 위해 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(authProvider)를 호출하는 컴포넌트는 다시 렌더링할 필요가 없다.
context 사용 사례
테마 설정: dark모드처럼 context provider를 앱 상단에 배치하고 시각적 모양을 조정해야 하는 컴포넌트에서 사용할 수 있다.
현재 계정: 많은 컴포넌트들이 현재 로그인한 사용자를 알아야 할 수 있다. 컨텍스트에 넣으면 트리 아무 곳에서나 쉽게 읽을 수 있기 때문이다.
라우팅: 대부분의 라우팅 솔루션은 현재 경로를 유지하기 위해 내부적으로 컨텍스트를 사용한다. 모든 링크가 활성 상태인지 아닌지를 알게 해 줄 수 있다.
상태 관리: Reducer를 context와 함께 사용하여 복잡한 상태를 관리하고 멀리 떨어진 컴포넌트로 전달하는 것이 일반적이다.
'개발자의 공부 > React' 카테고리의 다른 글
[React]Page Routing(v6.4) (0) | 2022.11.28 |
---|---|
[React]Google 로그인 OAuth2로 다른 Google API와 연동 (0) | 2022.11.27 |
[React]Purity and Side Effect (0) | 2022.11.08 |
[React]useEffect (0) | 2022.11.08 |
[React]useReducer (0) | 2022.10.24 |