Context๋? : Props Drilling์ ๋์
- Context๋ ์ปดํฌ๋ํธ์์ Props๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ์ฃผ๋ ๋ฐฉ์์ด๋ค.
ํน์ ๋ฐ์ดํฐ๋ฅผ ์ปดํฌ๋ํธ ํธ๋ฆฌ ์ ์ฒด์ ๊ฑธ์ณ ์ฝ๊ฒ ๊ณต์ ํ ์ ์๊ฒ ํด์ค๋ค. - Context๋ Props Drilling ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด ์ค ์ ์๋ค.
- Props Drilling : ์ค์ฒฉ๋ ์ฌ๋ฌ ๊ณ์ธต์ ์ปดํฌ๋ํธ์๊ฒ props ์ ๋ฌ.
- ๋ฌธ์ ์ : ํน์ prop์ ์ฌ์ฉํ์ง ์๋ ์ปดํฌ๋ํธ๋ค์๊ฒ๋ ํด๋น prop์ ๋ด๋ ค์ฃผ์ด์ผ ํ๋ ๋นํจ์จ. ๊ฐ๋ ์ฑ ํ๋ฝ. ์ปดํฌ๋ํธ ๊ฒฐํฉ๋ ์์น์ผ๋ก ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง ๋ฐ์ ๊ฐ๋ฅ, ์ฝ๋์ ๋ณต์ก๋ ์ฆ๊ฐ
- context๋ ๋ฆฌ์กํธ ๋ฒ์ 16๋ถํฐ ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฆฌ์กํธ ๋ด์ฅ API๋ก, ์ด๋ค ๋ฆฌ์กํธ ํ๋ก์ ํธ๋ผ๋ ๋ฆฌ์กํธ๋ฅผ import ํ๋ฉด context๋ฅผ ๋ฐ๋ก ์์ฑํ๊ณ ์ฌ์ฉํ ์ ์๋ค.
์ฌ์ฉ๋ฒ 3์ค ์์ฝ
- export const MyContext = createContext(defaultValue)์ ์ฌ์ฉํ์ฌ context๋ฅผ ๋ง๋ค๊ณ ๋ด๋ณด๋ธ๋ค.
- ์ด๋ฅผ useContext(MyContext) ํ ์ ์ ๋ฌํ๋ฉด ๊น์ด์ ์๊ด์์ด ๋ชจ๋ ํ์ ๊ตฌ์ฑ ์์์์ ์ฝ์ ์ ์๋ค.
- ํ์ ํญ๋ชฉ์ <MyContext.Provider value={...}> ๋ก ๋ฌถ์ด ๋ถ๋ชจ๋ก๋ถํฐ ์ ๊ณตํ๋ค.
1. context ์์ฑ : createContext
import { createContext } from 'react';
export const LevelContext = createContext(1);
๋ฆฌ์กํธ ํจํค์ง์์ createContext๋ผ๋ ํจ์๋ฅผ ๋ถ๋ฌ์์ ๋ง๋ ๋ค.
2. context ์ฌ์ฉ : useContext
export default function Heading({ level, children }) {
์์ ๊ฐ์ด ํ๋กญ์ผ๋ก ์ ๋ฌ๋ฐ๋ ๋ฐ์ดํฐ๋ฅผ
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
const level = useContext(LevelContext);
// ...
}
import ํ context๋ฅผ ํตํด ๊ฐ์ผ๋ก ์ฝ์ ์ ์๋ค.
useContext์ ์ธ์์๋ createContext๋ก ๋ง๋ LevelContext๋ฅผ ๋ฃ๋๋ค.
useContext : React ์ปดํฌ๋ํธ์ ์ต์์ ์์ค์์ ์ง์ ํธ์ถํด์ผ ํ๋ React์ ํ ์ค ํ๋์ด๋ค. (๋ฐ๋ณต๋ฌธ, ์กฐ๊ฑด๋ฌธ ๋ด๋ถ ํธ์ถ ๋ถ๊ฐ) useContext๋ฅผ ์ฌ์ฉํ์ฌ React ์ปดํฌ๋ํธ๊ฐ ํน์ ์ปจํ ์คํธ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ์ ํ๋ค๊ณ ๋ฆฌ์กํธ์๊ฒ ์ ๋ฌํ๋ค.
3. context ์ ๊ณต : Provider
context provider๋ก ๊ฐ์ธ์ค๋ค.
Context ๊ฐ์ฒด ์์๋ Provider๋ผ๋ ์ปดํฌ๋ํธ๊ฐ ๋ค์ด์๋ค. ๊ทธ ์ปดํฌ๋ํธ ๊ฐ์ ๊ณต์ ํ๊ณ ์ ํ๋ ๊ฐ์ value๋ผ๋ Props๋ก ์ค์ ํ๋ฉด ์์ ์ปดํฌ๋ํธ๋ค์์ ํด๋น ๊ฐ์ ๋ฐ๋ก ์ ๊ทผ์ ํ ์ ์๋ค.
import { LevelContext } from './LevelContext.js';
export default function Section({ level, children }) {
return (
<section className="section">
<LevelContext.Provider value={level}>
{children}
</LevelContext.Provider>
</section>
);
}
section ์์ ์ปดํฌ๋ํธ๊ฐ LevelContext๋ฅผ ์์ฒญํ๋ค๋ฉด ์ด ๋ ๋ฒจ์ ์ ๊ณตํ๋ผ๊ณ ๋ฆฌ์กํธ์๊ฒ ์ง์ํ๊ฒ ๋๋ค.
์ปดํฌ๋ํธ๋ ๊ทธ ์์ UI ํธ๋ฆฌ์์ ๊ฐ์ฅ ๊ฐ๊น์ด <LevelContext.Provider>์ ๊ฐ์ ์ฌ์ฉํ ๊ฒ์ด๋ค.
๊ฐ์ ์ปดํฌ๋ํธ๋ก๋ถํฐ context๋ฅผ use ํ๊ณ provide ํ๊ธฐ
์์ ์์์์ level์ ํ๋กญ์ผ๋ก ๋ด๋ ค์ค ํ์ ์์ด ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ ์ ์๋ค.
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>
);
}
์์ ์น์ ์ผ๋ก๋ถํฐ level์ ์ฝ์ด, ์๋์ผ๋ก +1์ ํ์ ์ปดํฌ๋ํธ์๊ฒ ์ ๋ฌํด ์ค๋ค.
์ด Section ์ปดํฌ๋ํธ๋ useContext(LevelContext)๋ฅผ ์ฌ์ฉํด ํ์ฌ ๋ ๋ฒจ์ ๊ฐ์ ธ์จ๋ค.
๋ํ <LevelContext.Provider value={level + 1}>์ ์ฌ์ฉํด ์์ ์ปดํฌ๋ํธ๋ค์๊ฒ ์์ ๋ ๋ ๋ฒจ ๊ฐ์ ์ ๊ณตํ๋ค.
-> ์ปดํฌ๋ํธ๋ค์ Context๋ฅผ ํตํด ์์ ์ "๊น์ด"๋ฅผ ํ์ ํ๊ฒ ๋๋ค.
Context๋ ์ค๊ฐ์ ์๋ ์ปดํฌ๋ํธ๋ฅผ ํต๊ณผํ๋ค.
context ๋ฐ์ดํฐ๋ ์ค๊ฐ์ ์๋ ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ค์ ํต๊ณผํ๊ณ ์ ๋ฌ๋๋ค. <div> ๊ฐ์ ๊ธฐ๋ณธ HTML ์ปดํฌ๋ํธ๋ ์ฌ์ฉ์๊ฐ ์ง์ ๋ง๋ ์ปดํฌ๋ํธ ์ฌ์ด์์๋ ์ ๋ฌ๋๋ค.
context์ ์๋ ๋ฐฉ์์ CSS์ property inheritance์ ์ ์ฌํ๋ค. ์๋ฅผ ๋ค์ด, CSS์์ <div>์ color: blue๋ฅผ ์ง์ ํ๋ฉด, ๊ทธ ์์ ์๋ ๋ชจ๋ ์์๋ค์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ด ์์์ ์์๋ฐ์ง๋ง, ๋ค๋ฅธ ์์๊ฐ color: green์ผ๋ก ์ด๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ ์ ์๋ค. React์์๋ ์์ ์ปดํฌ๋ํธ์์ ์ ๊ณตํ๋ ์ปจํ ์คํธ๋ ํ์ ์ปดํฌ๋ํธ์์ ๋ค๋ฅธ ๊ฐ์ผ๋ก ์ค๋ฒ๋ผ์ด๋ํ ์ ์๋ค.
๋ํ, ์๋ก ๋ค๋ฅธ ์ปจํ ์คํธ๋ ์๋ก๋ฅผ ์ค๋ฒ๋ผ์ด๋ ํ์ง ์๋๋ค. CSS์ color์ background-color๊ฐ ์๋ก ๋ค๋ฅธ ์์ฑ์ผ๋ก ์๋ํ๋ฏ์ด, createContext()๋ก ๋ง๋ ๊ฐ ์ปจํ ์คํธ๋ ์์ ํ ๋ณ๊ฐ์ด๋ค. ํ ์ปดํฌ๋ํธ๋ ์ฌ๋ฌ ๋ค๋ฅธ ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ ๊ณตํ ์ ์์ผ๋ฉฐ, ์ด๊ฒ๋ค์ด ์๋ก ๊ฐ์ญํ์ง ์๋๋ค.
ํ์ฉ ์ฌ๋ก
์ปค์คํ Hook ๋ง๋ค๊ธฐ
๋ง์ฝ Context๊ฐ ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ์ฌ์ฉ๋๊ณ ์๋ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ์ปค์คํ Hook์ ๋ง๋ค์ด์ ์ฌ์ฉํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด๋ค.
const MyContext = createContext();
function useMyContext() {
return useContext(MyContext);
}
function App() {
return (
<MyContext.Provider value="Hello World">
<AwesomeComponent />
</MyContext.Provider>
);
}
function AwesomeComponent() {
return (
<div>
<FirstComponent />
<SecondComponent />
<ThirdComponent />
</div>
);
}
function FirstComponent() {
const value = useMyContext();
return <div>First Component says: "{value}"</div>;
}
// ...
createContext ํจ์ ์ธ์์ ๊ธฐ๋ณธ ๊ฐ ๋ฃ๊ธฐ
๋ง์ฝ ์์ ์ปดํฌ๋ํธ์์ useContext๋ฅผ ์ฌ์ฉํ๊ณ ์๋๋ฐ, Provider ์ปดํฌ๋ํธ๋ก ๊ฐ์ธ๋ ๊ฒ์ ๊น๋นกํ๋ค๋ฉด?
value ๊ฐ์ ์ง์ ํ์ง ์์๊ธฐ ๋๋ฌธ์, undefined๋ก ์๋ฌด๊ฒ๋ ๋ํ๋์ง ์๊ฒ ๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ์ ๊ธฐ๋ณธ ๊ฐ์ ์ค์ ํ๊ณ ์ถ๋ค๋ฉด, createContext ํจ์์ ์ธ์๋ก ๊ธฐ๋ณธ ๊ฐ์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
const MyContext = createContext('default value');
๊ธฐ๋ณธ ๊ฐ์ ๋ณด์ฌ์ฃผ์ง ์๊ณ ์์ ์ค๋ฅ๋ฅผ ๋์ฐ๊ณ ์ถ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ปค์คํ ํ ์ ์์ ํ ์๋ ์๋ค.
const MyContext = createContext();
function useMyContext() {
const value = useContext(MyContext);
if (value === undefined) {
throw new Error('useMyContext should be used within MyContext.Provider');
}
}
Context์์ ์ํ ๊ด๋ฆฌ๊ฐ ํ์ํ ๊ฒฝ์ฐ (์ ๋์ ์ธ ๊ฐ์ ๋ค๋ค์ผ ํ ๋)
์ซ์๊ฐ ๋ณด์ด๋ UI์ ์ซ์์ ๋ณํ๋ฅผ ์ฃผ๋ UI๊ฐ ์์ ํ ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌ๋์ด ์๊ณ
์ด๋ฅผ Props๋ก ํจ์๋ ๊ฐ์ ์ ๋ฌํ๋ ๊ฒ์ด ์๋๋ผ Context๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํ๊ธฐ
๋จผ์ , Context์์ ์ ๋์ ์ธ ๊ฐ์ ๊ด๋ฆฌํ๋ ๊ฒฝ์ฐ์๋ Provider๋ฅผ ์๋ก ๋ง๋ค์ด์ฃผ๋ ๊ฒ์ด ์ข๋ค.
childeren Props๋ฅผ ๋ฐ์์์ CounterContext.Provider ํ๊ทธ ์ฌ์ด์ ๋ฃ๊ณ , ํ์ํ ๊ธฐ๋ฅ๋ค์ CounterProvider ์ปดํฌ๋ํธ ์์์ ๊ตฌํํด ์ฃผ๋ฉด ๋๋ค.
import {createContext} from 'react';
const CounterContext = createContext();
function CounterProvider({ childeren }) {
return <CounterContext.Provider>{children}</CounterContext.Provider>;
}
function App() {
return (
<CounterProvider>
<div>
<Value />
<Buttons />
</div>
</CounterProvider>
);
}
๋ง์ฝ ์ง๊ธ๊ณผ ๊ฐ์ด ํ๋์ ์ํ๋ง ์๋ ๊ฒฝ์ฐ๋ผ๋ฉด, useState๋ฅผ ์ฌ์ฉํ์ฌ ๋ง๋ค์ด์ง ๊ฐ๊ณผ ํจ์๊ฐ ๋ค์ด์๋ ๋ฐฐ์ด์ ํต์งธ๋ก value๋ก ๋ฃ๋๋ค.
import { createContext, useState } from 'react';
const CounterContext = createContext();
function CounterProvider({ children }) {
const counterState = useState(1);
return (
<CounterContext.Provider value={counterState}>
{children}
</CounterContext.Provider>
);
}
๊ทธ๋ค์์ useCounterState๋ผ๋ ์ปค์คํ Hook์ ๋ง๋ ๋ค.
import { createContext, useContext, useState } from 'react';
//...
function useCounterState() {
const value = useContext(CounterContext);
if (value === undefined) {
throw new Error('useCounterState should be used within CounterProvider');
}
return value;
}
์ด๋ ๊ฒ Hook์ ์ค๋นํด์ฃผ๊ณ ๋๋ฉด, CounterProvider์ ์์ ์ปดํฌ๋ํธ ์ด๋์๋ ์ง useCounterState๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ์กฐํํ๊ฑฐ๋ ๋ณ๊ฒฝํ ์ ์๋ค.
๋ง์ฝ์ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ์ ๋ฐ์ดํธํ ์ง์ ๋ํ ๋ก์ง์ ์ปดํฌ๋ํธ๊ฐ ์๋๋ผ Provider๋จ์์ ๊ตฌํํ๊ณ ์ถ์ผ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ ์๋ ์๋ค.
import {createContext, useContext, useMemo, useState} from 'react';
const CounterContext = createContext();
function CounterProvider({ children }) {
const [counter, setCounter] = useState(1);
const actions = useMemo(
() => ({
increase() {
setCounter((prev) => prev + 1);
},
decreaset() {
setCounter((prev) => prev - 1);
}
}),
[],
);
**const value = useMemo(() => [counter, actions], [counter, actions]);**
return (
<CounterContext.Provider value={value}>{children}</CounterContext.Provider>
);
}
// ์ปค์คํ
Hook
function useCounter() {
const value = useContext(CounterContext);
if (value === undefined) {
throw new Error('useCounterState should be used within CounterProvider');
}
rturn value;
}
function App() {
return (
<CounterProvider>
<div>
<Value />
<Buttons />
</div>
</CounterProvider>
);
}
function Value() {
const [counter] = useCounter();
const <h1>{counter}</h1>;
}
function Buttons() {
const [, actions] = useCounter();
return (
<div>
<button onClick={actions.increase}>+</button>
<button onClick={actions.decrease}>-</button>
</div>
);
}
export default App;
์ ์ฝ๋์์๋ actions์ด๋ผ๋ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ๊ทธ ์์ ๋ณํ๋ฅผ ์ผ์ผํค๋ ํจ์๋ค์ ๋ฃ์๊ณ , ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋ ๋๋ง๋ค ํจ์๋ฅผ ์๋ก ๋ง๋๋ ๊ฒ ์๋๋ผ ์ฒ์์ ํ๋ฒ ๋ง๋ค๊ณ ๊ทธ ์ดํ์ ์ฌ์ฌ์ฉํ ์ ์๋๋ก useMemo๋ก ๊ฐ์ธ์ฃผ์๋ค.
value์ ๊ฐ์ ๋ฃ์ด์ฃผ๊ธฐ ์ ์ [counter, action]์ useMemo๋ก ํ๋ฒ ๊ฐ์ธ์ฃผ์๋ค. useMemo๋ก ๊ฐ์ธ์ง ์์ผ๋ฉด CounterProvider๊ฐ ๋ฆฌ๋ ๋๋ง ๋ ๋๋ง๋ค ์๋ก์ด ๋ฐฐ์ด์ ๋ง๋ค๊ธฐ ๋๋ฌธ์ useContext๋ฅผ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ ์ชฝ์์ Context์ ๊ฐ์ด ๋ฐ๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผํ๊ฒ ๋์ด ๋ญ๋น ๋ ๋๋ง์ด ๋ฐ์ํ๊ฒ ๋๋ค.
์ํ๊ฐ ๋น๋ฒํ๊ฒ ์ ๋ฐ์ดํธ๋ ๋ : ๊ฐ๊ณผ ์ ๋ฐ์ดํธ ํจ์๋ฅผ ๋ ๊ฐ์ Context๋ก ๋ถ๋ฆฌํ๊ธฐ
Context์์ ๊ด๋ฆฌํ๋ ์ํ๊ฐ ๋น๋ฒํ๊ฒ ์ ๋ฐ์ดํธ๋์ง ์๋๋ค๋ฉด, ์ ์ฝ๋๋ง์ผ๋ก๋ ์ถฉ๋ถํ์ง๋ง
์ํ๊ฐ ๋น๋ฒํ๊ฒ ์ ๋ฐ์ดํธ๋๋ค๋ฉด ์ฑ๋ฅ์ ์ผ๋ก ์ข์ง ์๋ค.
๋ณํ๊ฐ ๋ฐ์๋๋ ๊ณณ์ Value ์ปดํฌ๋ํธ๋ฟ์ธ๋ฐ Buttons ์ปดํฌ๋ํธ๋ ๋ฆฌ๋ ๋๋ง ๋๊ธฐ ๋๋ฌธ์ด๋ค.
Context๋ฅผ ํ๋ ๋ ๋ง๋ค์ด์ ์ด๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
import { createContext, useContext, useMemo, useState } from 'react';
const CounterValueContext = createContext();
const CounterActionsContext = createContext();
function CounterProvider({ children }) {
const [counter, setCounter] = useState(1);
const actions = useMemo(
() => ({
increase() {
setCounter((prev) => prev + 1);
},
decrease() {
setCounter((prev) => prev - 1);
}
}),
[]
);
return (
<CounterActionsContext.Provider value={actions}>
<CounterValueContext.Provider value={counter}>
{children}
</CounterValueContext.Provider>
</CounterActionsContext.Provider>
);
}
function useCounterValue() {
const value = useContext(CounterValueContext);
if (value === undefined) {
throw new Error('useCounterValue should be used within CounterProvider');
}
return value;
}
function useCounterActions() {
const value = useContext(CounterActionsContext);
if (value === undefined) {
throw new Error('useCounterActions should be used within CounterProvider');
}
return value;
}
function App() {
return (
<CounterProvider>
<div>
<Value />
<Buttons />
</div>
</CounterProvider>
);
}
function Value() {
console.log('Value');
const counter = useCounterValue();
return <h1>{counter}</h1>;
}
function Buttons() {
console.log('Buttons');
const actions = useCounterActions();
return (
<div>
<button onClick={actions.increase}>+</button>
<button onClick={actions.decrease}>-</button>
</div>
);
}
export default App;
๊ธฐ์กด์ CounterContext๋ฅผ CounterValueContext์ CounterActionsContext๋ก ๋ถ๋ฆฌํด ์ฃผ๊ณ , ๋ Provider๋ฅผ ๋ชจ๋ ์ฌ์ฉํ๊ณ ์ปค์คํ Hook ๋ํ ๋ ๊ฐ๋ก ๋ถ๋ฆฌ.
์ด์ ๋ฒํผ์ ๋๋ฌ์ ์ํ์ ๋ณํ๊ฐ ์ผ์ด๋ ๋, Value ์ปดํฌ๋ํธ์์๋ง ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ค.
์์์๋ ์ํ๋ฅผ ๋ค๋ฃจ๋ Context๋ฅผ ํ์ฉํ๋ ๊ณผ์ ์์ ์ฌ๋ฌ ์ ๋ฐ์ดํธ ํจ์๊ฐ ๋ค์ด์๋ actions ๊ฐ์ฒด๋ฅผ ์ ์ธํ์ฌ ๋ณ๋์ Context์ ๋ฃ์ด์ฃผ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํ์ ํด์ฃผ์๋ค.
์ด ๋ฐฉ์ ์ธ์๋, useReducer๋ฅผ ํตํด์ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ํ๋๋ก ๊ตฌํํ๊ณ dispatch๋ฅผ ๋ณ๋์ Context๋ก ๋ฃ์ด์ฃผ๋ ๋ฐฉ์๋ ์๋ค.
์ฐธ๊ณ : ์๋ ๋งํฌ
How to use React Context effectively
Context์ ์ํ์์ ๋ฐฐ์ด์ด๋ ๊ฐ์ฒด๋ฅผ ๋ค๋ฃจ๋ ๊ฒฝ์ฐ
ํ๋ฉด์ ์ค์์ ๋ฌธ๊ตฌ๋ฅผ ๋์ฐ๋ ๋ชจ๋ฌ์ ์ํ๋ฅผ Context๋ก ์์ฑํ๋ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํํ ์ ์๋ค.
const ModalValueContext = createContext();
const ModalActionsContext = createContext();
function ModalProvider({ children }) {
const [modal, setModal] = useState({
visible: false,
message: ''
});
const actions = useMemo(
() => ({
open(message) {
setModal({
message,
visible: true
});
},
close() {
setModal((prev) => ({
...prev,
visible: false
}));
}
}),
[]
);
return (
<ModalActionsContext.Provider value={actions}>
<ModalValueContext.Provider value={modal}>
{children}
</ModalValueContext.Provider>
</ModalActionsContext.Provider>
);
}
function useModalValue() {
const value = useContext(ModalValueContext);
if (value === undefined) {
throw new Error('useModalValue should be used within ModalProvider');
}
return value;
}
function useModalActions() {
const value = useContext(ModalActionsContext);
if (value === undefined) {
throw new Error('useModalActions should be used within ModalProvider');
}
return value;
}
์ด๋ ๊ฒ ํ๋ฉด ์ํ๋ ๊ณณ ์ด๋์๋ ์ง ๋ค์๊ณผ ๊ฐ์ด ๋ชจ๋ฌ์ ๋์ธ ์ ์๋ค!
const { open } = useModalActions();
const handleSomething = () => {
open('์๋
!');
};
๋ง์ฝ ํ ์ผ ๋ชฉ๋ก ๊ฐ์ ๋ฐฐ์ด์ด๋ผ๋ฉด?
import { createContext, useContext, useMemo, useRef, useState } from 'react';
const TodosValueContext = createContext();
const TodosActionsContext = createContext();
function TodosProvider({ children }) {
const idRef = useRef(3);
const [todos, setTodos] = useState([
{
id: 1,
text: '๋ฐฅ๋จน๊ธฐ',
done: true
},
{
id: 2,
text: '์ ์๊ธฐ',
done: false
}
]);
const actions = useMemo(
() => ({
add(text) {
const id = idRef.current;
idRef.current += 1;
setTodos((prev) => [
...prev,
{
id,
text,
done: false
}
]);
},
toggle(id) {
setTodos((prev) =>
prev.map((item) =>
item.id === id
? {
...item,
done: !item.done
}
: item
)
);
},
remove(id) {
setTodos((prev) => prev.filter((item) => item.id !== id));
}
}),
[]
);
return (
<TodosActionsContext.Provider value={actions}>
<TodosValueContext.Provider value={todos}>
{children}
</TodosValueContext.Provider>
</TodosActionsContext.Provider>
);
}
function useTodosValue() {
const value = useContext(TodosValueContext);
if (value === undefined) {
throw new Error('useTodosValue should be used within TodosProvider');
}
return value;
}
function useTodosActions() {
const value = useContext(TodosActionsContext);
if (value === undefined) {
throw new Error('useTodosActions should be used within TodosProvider');
}
return value;
}
ํ ์ผ ํญ๋ชฉ์ ์ถ๊ฐํ ๋๋
const { add } = useTodosActions();
const handleSubmit = () => {
add(text);
}
๊ฐ ํญ๋ชฉ์ ๋ณด์ฌ์ฃผ๋ ์ปดํฌ๋ํธ์์๋
const { toggle, remove } = useTodosActions()
const handleToggle = () => {
toggle(id);
};
const handleRemove = () => {
remove(id);
};
๊ณผ๊ฑฐ์๋ ์์ ๊ฐ์ ์์ ์ ํ๊ธฐ ์ํ์ฌ useReducer์ ์ฌ์ฉํ๊ธฐ๋ ํ์ง๋ง, ์ก์ /๋ฆฌ๋์ ๋ฐฉ์ ๊ตณ์ด? ์์ ๊ฐ์ ๋ฐฉ์์ด ๋ ํธํ ๊ฑฐ๋ค.
Context๊ฐ ๊ผญ ์ ์ญ์ ์ผ ํ์๋ ์๋ค.
Context์์ ๋ค๋ฃจ๋ ๊ฐ์ ๊ผญ ์ ์ญ์ ์ผ ํ์๊ฐ ์๋ค. Context๋ ์ฌ์ฌ์ฉ์ฑ์ด ๋์ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ๋๋ ๋งค์ฐ ์ ์ฉํ๋ค!
import { useState } from 'react';
function Item({ active, children, onClick }) {
const activeStyle = {
backgroundColor: 'black',
color: 'white'
};
const style = {
cursor: 'pointer',
padding: '1rem'
};
return (
<div
style={active ? { ...style, ...activeStyle } : style}
onClick={onClick}
>
{children}
</div>
);
}
function App() {
const [activeId, setActiveId] = useState(1);
return (
<div>
<Item active={activeId === 1} onClick={() => setActiveId(1)}>
Hello
</Item>
<Item active={activeId === 2} onClick={() => setActiveId(2)}>
World
</Item>
<Item active={activeId === 3} onClick={() => setActiveId(3)}>
React
</Item>
</div>
);
}
export default App;
active ๊ฐ์ ์ค์ ํ๊ธฐ ์ํด์ ๊ฐ ์ปดํฌ๋ํธ๋ง๋ค id๋ฅผ ๋น๊ตํ๊ณ , onClick๋ ๊ฐ id์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์๋ก์ด ํจ์๋ฅผ ์ ์ธํ์ฌ ์ค์ ํด ์ฃผ์๋ค.
์ด๊ฑธ ์์ id๋ฅผ Props๋ก ๋ฃ๋ ๋ฐฉ์์ผ๋ก ๋ฆฌํฉํ ๋ง์ ํ๋ค๊ณ ํด๋ ์ปดํฌ๋ํธ ์ฌ์ฉ ์ ๊ฐ๋ ์ฑ์ด ๊ทธ๋ฆฌ ๋ง์กฑ์ค๋ฝ์ง ์๋ค.
import { useState } from 'react';
function Item({ activeId, children, onSelect, id }) {
const activeStyle = {
backgroundColor: 'black',
color: 'white'
};
const style = {
cursor: 'pointer',
padding: '1rem'
};
const active = activeId === id;
const onClick = () => onSelect(id);
return (
<div
style={active ? { ...style, ...activeStyle } : style}
onClick={onClick}
>
{children}
</div>
);
}
function App() {
const [activeId, setActiveId] = useState(1);
return (
<div>
<Item id={1} activeId={activeId} onSelect={setActiveId}>
Hello
</Item>
<Item id={2} activeId={activeId} onSelect={setActiveId}>
World
</Item>
<Item id={3} activeId={activeId} onSelect={setActiveId}>
React
</Item>
</div>
);
}
export default App;
์ฌ์ฉํ ๋๋ง๋ค activeId, onSelect Props๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ๋ฃ์ด์ค์ผ ํ๋ ๊ฒ ์ข ๋ถํธํ๋ค.
๋ฌผ๋ก ๋ค์๊ณผ ๊ฐ์ด ๋ฐฐ์ด์ map ๋ด์ฅํจ์๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ฐ๋ณต๋๋ ์ฝ๋๋ ์ฌ๋ผ์ง๊ฒ ์ง๋ง, ํญ๋ชฉ๋ค์ JSX๋ก ๋ช ๋ฃํ๊ฒ ํํํด๋ด์ง ๋ชปํ๋ค๋ ์ ์ด ์์ฌ์ธ ์๋ ์๋ค.
function App() {
const [activeId, setActiveId] = useState(1);
const items = [
{ id: 1, text: 'Hello' },
{ id: 2, text: 'World' },
{ id: 3, text: 'React' }
];
return (
<div>
{items.map((item) => (
<Item
key={item.id}
id={item.id}
activeId={activeId}
onSelect={setActiveId}
>
{item.text}
</Item>
))}
</div>
);
}
→ Context์ ํด๊ฒฐ์ฑ !
๋ง์ฝ์ ํญ๋ชฉ๋ค์ JSX๋ก ํํํ๊ณ ์ถ๊ณ , ๋ฐ๋ณต๋๋ ์ฝ๋๋ค์ ์ ๋ฆฌํด์ฃผ๊ณ ์ถ๋ค๋ฉด, ์ด ๋ํ Context๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๊ฒ ํด๊ฒฐํ ์ ์๋ค!
import { createContext, useContext, useMemo, useState } from 'react';
const ItemGroupContext = createContext();
function ItemGroup({ children, activeId, onSelect }) {
const value = useMemo(
() => ({
activeId,
onSelect
}),
[activeId, onSelect]
);
return (
<ItemGroupContext.Provider value={value}>
{children}
</ItemGroupContext.Provider>
);
}
function useItemGroup() {
const value = useContext(ItemGroupContext);
if (value === undefined) {
throw new Error('Item component should be used within ItemGroup');
}
return value;
}
function Item({ children, id }) {
const activeStyle = {
backgroundColor: 'black',
color: 'white'
};
const style = {
cursor: 'pointer',
padding: '1rem'
};
const { activeId,onSelect } = useItemGroup();
const active = activeId === id;
const onClick = () => onSelect(id);
return (
<div
style={active ? { ...style, ...activeStyle } : style}
onClick={onClick}
>
{children}
</div>
);
}
function App() {
const [activeId, setActiveId] = useState(1);
const [anotherActiveId, setAnotherActiveId] = useState(4);
return (
<div>
<ItemGroup activeId={activeId} onSelect={setActiveId}>
<Item id={1}>Hello</Item>
<Item id={2}>World</Item>
<Item id={3}>React</Item>
</ItemGroup>
<hr />
<ItemGroup activeId={anotherActiveId} onSelect={setAnotherActiveId}>
<Item id={4}>Bye</Item>
<Item id={5}>World</Item>
<Item id={6}>Context</Item>
</ItemGroup>
</div>
);
}
export default App;
ํ์ํ ๊ฐ๊ณผ ํจ์๋ฅผ ๋งค๋ฒ Props๋ก ๋ฃ์ด์ฃผ๋ ๋์ , ItemGroup์ด๋ผ๋ Provider ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด์ ํด๋น ์ปดํฌ๋ํธ์๋ง ํ๋ฒ ๋ฃ์ด์ฃผ๊ณ , Item์์ Context๋ฅผ ์ฝ์ด์์ ๊ฐ์ ์ฌ์ฉํ๋๋ก ๋ง๋ค์ด์ฃผ์๋ค.
๋น๋ก ์์ฑํด์ผ ํ ์ ์ฒด์ ์ธ ์ฝ๋๋ ์กฐ๊ธ ๋์์ง๋ง, Item ์ฝ๋๋ฅผ ์ฌ์ฉํ๋ ์ชฝ์์๋ ํจ์ฌ ๊ฐ๋ ์ฑ ๋๊ณ ํธํ๊ฒ ์์ฑํ ์ ์๊ณ ์ฌ์ฌ์ฉ์ฑ ๋ํ ์ข์์ก๋ค.
์ด๋ ๊ฒ, Context๋ฅผ ๊ผญ ์ ์ญ์ ์ธ ๊ฐ์ ์ํด์๋ง ์ฐ๋ ๊ฒ ์๋๋ผ Props๊ฐ ์๋ ๋ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ด๋ผ๋ ์ ๊ทผ์ ํ์ฌ ์ฌ์ฉํ๋ค๋ฉด ๋ค์ํ ์ํฉ์ ์ ์ฉํ๊ฒ ์ธ ์ ์๋ค!
์ ์ญ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ธ์ ?
๊ณผ๊ฑฐ์๋ ๋ฆฌ์กํธ์ Context๊ฐ ๊ต์ฅํ ๋ถํธํด์ ์ ์ญ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋น์ฐ์ ์ฌ๊ฒจ์ง๊ณค ํ์ง๋ง ์ด์ ๋ ์ฌ์ฉํ๊ธฐ ํธํด์ ธ์ ๋จ์ํ ์ ์ญ ์ํ๋ฅผ ์ํด์๋ผ๋ฉด ์ฌ์ฉํ์ง ์์๋ ๋๋ค.
๋จ, “์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ”์ Context๋ ์์ ํ ๋ณ๊ฐ์ ๊ฐ๋ ์์ ์ ์์์ผ ํ๋ค. Context๋ ์ ์ญ ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์๋ ์๋จ์ผ ๋ฟ์ด๊ณ , ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ํ ๊ด๋ฆฌ๋ฅผ ๋์ฑ ํธํ๊ณ ํจ์จ์ ์ผ๋ก ํ ์ ์๊ฒ ๊ธฐ๋ฅ์ ์ ๊ณตํด ์ฃผ๋ ๋๊ตฌ์ด๋ค.
Redux
- ์ก์ ๊ณผ ๋ฆฌ๋์ ๊ฐ๋ ์ ์ฌ์ฉํ์ฌ ์ํ ์ ๋ฐ์ดํธ ๋ก์ง์ ์ปดํฌ๋ํธ ๋ฐ์ผ๋ก ๋ถ๋ฆฌ
- ์ํ๊ฐ ์ ๋ฐ์ดํธ๋ ๋ ์ค์ ๋ก ์์กดํ๋ ๊ฐ์ด ๋ฐ๋ ๋๋ง ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋๋๋ก ์ต์ ํ
- Context๋ฅผ ์ด๋ค๋ฉด ๊ฐ๊ธฐ ๋ค๋ฅธ ์ํ๋ง๋ค Context๋ฅผ ๋ง๋ค์ด์ฃผ์ด์ผ ํ๋๋ฐ ์ด ๊ณผ์ ์๋ต ๊ฐ๋ฅ
MobX
- Redux์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ํ ์ ๋ฐ์ดํธ ๋ก์ง์ ์ปดํฌ๋ํธ ๋ฐ์ผ๋ก ๋ถ๋ฆฌ
- ํจ์ํ ๋ฆฌ์กํธ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ๋์ ํ์ฌ mutable ํ ์ํ๊ฐ ๋ฆฌ์กํธ์์๋ ์ ๋ณด์ด๊ฒ ํ๊ณ
- ์ํ ์ ๋ฐ์ดํธ ๋ก์ง์ ๋์ฑ ํธํ๊ฒ ์์ฑ, ์ต์ ํ๋ ์ํด์ค๋ค
Recoil, Jotai, Zustand
- Context๋ฅผ ์ผ์ผ์ด ๋ง๋๋ ๊ณผ์ ์๋ต
- Hook ๊ธฐ๋ฐ์ผ๋ก ํธํ๊ฒ ์ ์ญ ์ํ ใ ๊ฐ๋ฆฌ
- ์ต์ ํ ๊ธฐ๋ฅ
→ ์ ์ญ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๊ฒฐ๊ตญ ์ํ ๊ด๋ฆฌ๋ฅผ ์ข ๋ ์ฝ๊ฒ ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๊ฒ์ด๋ฉฐ, ์ทจํฅ์ ๋ฐ๋ผ ์ ํํด์ ์ฐ๋ฉด ๋๋ค.
ref
https://react.dev/learn/passing-data-deeply-with-context
Passing Data Deeply with Context – React
The library for web and native user interfaces
react.dev
https://velog.io/@velopert/react-context-tutorial
๋ค๋ฅธ ์ฌ๋๋ค์ด ์ ์๋ ค์ฃผ๋ ๋ฆฌ์กํธ์์ Context API ์ ์ฐ๋ ๋ฐฉ๋ฒ
์ฌ๋ฌ๋ถ, ๋ฆฌ์กํธ๋ก ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ํ๋ฉด์ Context API๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ๊ณ ๊ณ์ ๊ฐ์? ๊ณผ๊ฑฐ์๋ ๊ด๋ จ ํฌ์คํธ๋ฅผ ์์ฑํ์ ์ด ์๊ธด ํ์ง๋ง, ์ง๋ ๋ช ๋ ๊ฐ Context๋ฅผ ์ฌ์ฉํ๋ฉด์ ์ต๋ํ๊ฒ๋ ํ๋ค์
velog.io