[TIL/Coin Site Project] 2023/12/01
reference: https://react.dev/reference/react/createContext https://react.dev/reference/react/useContext#passing-data-deeply-into-the-tree > createCon
reference: 1. https://react.dev/reference/react/createContext 2. https://react.dev/reference/react/useContext#passing-data-deeply-into-the-tree
createContext ✍️
1. 정의 🔴
### 2. 사용법 🔴
const SomeContext = createContext(defaultValue)
변수명은 자유롭게 지정해도 좋다. 다만 '(some)Context'로 명명하는 것이 관습이다. parameter로는 defaultValue를 받는다.
=> createContext를 명확히 이해하기 위해서는 결국 useContext를 살펴봐야 한다.
> **useContext ✍️**
### 1. 정의 🔴
2. 사용법 & 구체적인 사용 Case 🔴
const value = useContext(SomeContext)createContext에서 생성한 SomeContext라는 하나의 Context가 useContext에 parameter로 전달되는 모습을 확인할 수 있다.
#### 2-1. Passing data deeply into the tree 🟢
data를 tree에 깊게 전달하는 상황에서 고려해야 할 사항은 다음과 같다.
1. useContext는 컴포넌트의 ``top level에서 호출``해야 한다. 2. useContext는 context value를 반환하는데, 이때 ``the closest context provider``를 기준으로 context value를 결정한다. 3. useContext를 사용하는 컴포넌트가 아니라, ``upper level 컴포넌트가 Provider로 wrapping``되어 있어야 한다.
아래 코드는, 위에서 언급한 모든 고려사항이 투영된 예제 코드이다. 개별 고려사항은 주석으로 표시했다.
import { createContext, useContext } from "react";
// context 생성
const ThemeContext = createContext(null);
export default function MyApp() {
return (
// useContext 사용처(Panel)의 upper level이 Provider로 wrapping되어 있어야 함
<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 }) {
// component의 top level에서 useContext를 호출
const theme = useContext(ThemeContext);
const className = "panel-" + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
);
}
function Button({ children }) {
// the closest context provider(ThemeContext.Provider)로부터 context value를 결정
const theme = useContext(ThemeContext);
const className = "button-" + theme;
return <button className={className}>{children}</button>;
}
#### 2-2. Updating data passed via context 🟢
context를 통해 전달된 data를 update하기 위해 고려해야 할 사항은 다음과 같다.
1. context의 update를 위해서 ``state와 결합``한다. 2. 부모 component에 state를 선언하고, ``Provider에 value로 전달``한다.
바로, 코드를 통해 살펴보자.
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 !== '' && 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>
);
}

코드는 길지만 결국 말하고자 하는 바는 다음과 같다.
context의 update를 위해서 context가 state와 결합될 필요가 있다. 그 방법으로, 부모 component에 state를 선언하여 Provider에 value로 전달하는 방식을 취한다.
#### 2-3. Specifying a fallback default value 🟢
context의 생성과 활용에 있어 '대체 기본값'을 지정하는 부분도 살펴봐야 한다.
1. 부모 트리에서 ``Provider를 찾지 못한 경우`, useContext의 return 값은 `default value``이다. 2. default value는 결코 ``변하지 않기에``, 값의 update를 위해서는 state를 사용해야 한다. 3. 그럼에도 default value를 설정해 놓음으로써, ``렌더링 오류를 방지``할 수 있다.
코드를 살펴보자.
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>
);
}MyApp에서 Button과 ThemeContext는 동일한 계층에 존재한다.
Button 함수에서 useContext를 사용하고 있는데 Provider가 상위 계층이 아니라 동일 계층에 있다는 것이다. 이 상황이 앞서 언급한 'Provider를 찾지 못한 경우'에 해당한다. 따라서 Toogle theme이라는 버튼의 색상은 default value에 의해 결정된다. ThemeContext의 초기값을 dark로 변경하면 해당 버튼은 검정색으로 렌더링된다.
최소한 Provider의 계층적 불일치가 있다고 해서 오류를 반환하지는 않게 되는 것이다.
#### 2-4. Overriding context for a part of the tree 🟢
overriding에 관련된 case도 있다.
1. ``상이한 값`을 가진 Provider를 `overriding`` 할 수 있다.
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에서 렌더링 되는 버튼은 overriding, 즉 light로 재정의 된 provider의 값을 활용한다. 이에 반해, Panel의 버튼은 재정의 되기 전인 dark의 영향을 받는다. 따라서 Footer의 버튼은 흰색이고, Panel의 버튼은 검정이다.
#### 2-5. Optimizing re-renders when passing objects and functions 🟢
useContext에 의해 불필요한 re-rendering이 발생하게 되고, 최적화를 위해서 useMemo와 useCallback 등을 활용하여 퍼포먼스를 향상할 수 있다는 것이 공식문서의 주된 골자이다.
Optimizing에 관해서는 차후에 필요를 느끼게 될 때 정리하도록 하겠다.
다음 미션 ✍️
1. MUI 공식문서 읽고, 'theme custom'에 대해 알아보고 프로젝트에 적용하기 2. Provider에 관한 커스텀 훅 만들어보기
More to read
Amazon VPC Architecture 이해하기
새로운 프로젝트를 기획하며, 개발에서 무엇을 가장 먼저 고민해야 하는지 다시 돌아보게 되었습니다.한때는 프론트엔드가 모든 설계의 출발점이라고 믿었습니다. 유저가 무엇을 보고, 어떤 흐름에서 머무르고 이탈하는지에 대한 이해 없이 서비스를 만든다는 건 불가능하다고 생각했기
'원사이트'프론트엔드 관점으로 알고리즘 이해하기
오랜만에 방법론에 관한 글을 쓰게 되었습니다. 최근 상황은 이렇습니다. SSAFY에서는 하루에 엄청난 양의 알고리즘 문제들을 과제로 수행하게 됩니다. 그 과정에서, '구현력'이 매우 떨어진다는 생각이 들었습니다. 완전히 어려운 문제라면 '아쉬움'이라는 감정조차 느끼지
SubnetVPC 설계의 시작: IP와 Subnet
반복되는 루틴 속에서 얻은 안정감을 발판 삼아, 이제는 기술적 스펙트럼을 넓히기 위한 개인 프로젝트에 착수하고자 합니다.이번 프로젝트의 목표는 단순한 포트폴리오 구축을 넘어, 실제 서비스 수준의 블로그 시스템 구현과 다국어 처리 적용 등 실무에 가까운 역량을 한 단계