React

Context API ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š” ๊ฒฝ์šฐ ์•Œ์•„๋ณด๊ธฐ

presentKey 2024. 8. 13. 01:57

๐Ÿ‘ ์ฐธ๊ณ 

1. https://solo5star.dev/posts/42/

2. https://velog.io/@velopert/react-context-tutorial#%EA%B0%92%EA%B3%BC-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%ED%95%A8%EC%88%98%EB%A5%BC-%EB%91%90%EA%B0%9C%EC%9D%98-context%EB%A1%9C-%EB%B6%84%EB%A6%AC%ED%95%98%EA%B8%B0

3. https://velog.io/@dahyeon405/Context-API%EC%9D%98-%EB%A6%AC%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%8C%80%ED%95%9C-%EC%98%A4%ED%95%B4

 

๐Ÿ‘€ Provider ๋‚ด๋ถ€์— ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ (<Text />)

const CounterValueContext = createContext(0);
const CounterActionContext = createContext({
  increase: () => {},
  decrease: () => {},
});

export function CounterProvider({ children }: { children: React.ReactNode }) {
  const [counter, setCounter] = useState(0);
  const increase = () => setCounter((prev) => prev + 1);
  const decrease = () => setCounter((prev) => prev - 1);
  console.log('Counter Provider');

  return (
    <CounterValueContext.Provider value={counter}>
      <CounterActionContext.Provider value={{ increase, decrease }}>
        <Text />
        {children}
      </CounterActionContext.Provider>
    </CounterValueContext.Provider>
  );
}

export function useCounter() {
  const value = useContext(CounterValueContext);
  return value;
}

export function useSetCounter() {
  const value = useContext(CounterActionContext);
  return value;
}
export default function ContextPage() {
  console.log('Context Page');
  return (
    <CounterProvider>
      <Plus />
      <Number />
      <Minus />
      <Hello />
    </CounterProvider>
  );
}
// Text.tsx
export default function Text() {
  console.log('Text');
  return <h3>Counter Text</h3>;
}

// Plus.tsx
export default function Plus() {
  const { increase } = useSetCounter();
  console.log('Plus');

  return <button onClick={increase}>+</button>;
}

// Minus.tsx
export default function Minus() {
  const { decrease } = useSetCounter();
  console.log('Minus');

  return <button onClick={decrease}>-</button>;
}

// Number.tsx
export default function Number() {
  const count = useCounter();
  console.log('Number');

  return <div>{count}</div>;
}

// Hello.tsx
export default function Hello() {
  console.log('Hello');
  return <div>Hello!</div>;
}

<Hello /> ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ์™ธํ•œ <Text />, <Plus />, <Minus />, <Number /> ์ปดํฌ๋„ŒํŠธ ๋ชจ๋‘ counter ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋ ๋•Œ๋งˆ๋‹ค ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•œ๋‹ค.

 

<Text />๋Š” CounterProvider์˜ ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌ๋ Œ๋”๋ง๋˜์—ˆ๊ณ , ๋ฆฌ๋ Œ๋”๋ง ๋˜๋ฉด์„œ increase, decrease ํ•จ์ˆ˜๋„ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— context์˜ ๊ฐ’์„ ๋ฐ”๋ผ๋ณด๊ณ  ์žˆ๋Š”(useCounter, useSetCounter) ๋ชจ๋“  ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๋ฆฌ๋ Œ๋”๋ง ๋˜์—ˆ๋‹ค.

 

  • ๋ฆฌ๋ Œ๋”๋ง์„ ๋ง‰๋Š” ๋ฐฉ๋ฒ•

context์˜ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€ ์•Š์€ <Text />์˜ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ง‰๋Š” ๋ฐฉ๋ฒ•์€ <Text /> ์ปดํฌ๋„ŒํŠธ์— React memo ๋ฅผ ์ ์šฉํ•˜๊ฑฐ๋‚˜ <Hello /> ์ปดํฌ๋„ŒํŠธ์ฒ˜๋Ÿผ children์œผ๋กœ ๋ Œ๋”๋ง ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

 

increase, decrease ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ง‰๋Š” ๋ฐฉ๋ฒ•์€ useMemo๋ฅผ ์ ์šฉํ•œ actions ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋Ÿฌ๋ฉด <CounterProvider />, <Number /> ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ฆฌ๋ Œ๋”๋ง ๋œ๋‹ค.

export function CounterProvider({ children }: { children: React.ReactNode }) {
  const [counter, setCounter] = useState(0);
  const actions = useMemo(
    () => ({
      increase() {
        setCounter((prev) => prev + 1);
      },
      decrease() {
        setCounter((prev) => prev - 1);
      },
    }),
    [],
  );
  console.log('Counter Provider');

  return (
    <CounterValueContext.Provider value={counter}>
      <CounterActionContext.Provider value={actions}>
        {children}
      </CounterActionContext.Provider>
    </CounterValueContext.Provider>
  );
}

increase, decrease ๊ฐ ํ•จ์ˆ˜์— useCallback์„ ์ ์šฉํ•ด value = {{ increase, decrease }} ๋กœ ์ „๋‹ฌํ•œ ๊ฒฝ์šฐ, ๋ Œ๋”๋ง ์ตœ์ ํ™”๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค. ์™œ๋ƒํ•˜๋ฉด <CounterProvider />๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜๋ฉด์„œ increase, decrease ํ•จ์ˆ˜๋Š” ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ๋์ง€๋งŒ, value๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฐ์ฒด๋Š” ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ง€๊ธฐ๋•Œ๋ฌธ์— ์ตœ์ ํ™”๊ฐ€ ๋˜์ง€ ์•Š๋Š”๋‹ค. 

 

๋งŒ์•ฝ increase ํ•จ์ˆ˜ ํ•˜๋‚˜๋งŒ ์ „๋‹ฌํ•œ๋‹ค๋ฉด useCallback๋งŒ์œผ๋กœ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿ‘€ children์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง ํ•œ ๊ฒฝ์šฐ

export function CounterProvider({ children }: { children: React.ReactNode }) {
  const [counter, setCounter] = useState(0);
  const actions = useMemo(
    () => ({
      increase() {
        setCounter((prev) => prev + 1);
      },
      decrease() {
        setCounter((prev) => prev - 1);
      },
    }),
    [],
  );
  console.log('Counter Provider');

  return (
    <CounterValueContext.Provider value={counter}>
      <CounterActionContext.Provider value={actions}>
        {children}
      </CounterActionContext.Provider>
    </CounterValueContext.Provider>
  );
}
export default function ContextPage() {
  console.log('Context Page');
  return (
    <CounterProvider>
      <Text>
        <HeadingNumber />
      </Text>
      <Plus />
      <Number />
      <Minus />
      <Hello />
    </CounterProvider>
  );
}
// Text.tsx
export default function Text({ children }: { children: React.ReactNode }) {
  console.log('Text');

  return (
    <h3>
      Counter Text
      {children}
    </h3>
  );
}

// HeadingNumber.tsx
export default function HeadingNumber() {
  const count = useCounter();
  console.log('Heading Number');

  return <span> โ˜… {count} โ˜…</span>;
}

// Hello.tsx
export default function Hello() {
  console.log('Hello');
  const count = useCounter();

  return <div>Hello!</div>;
}

- <Text />๋Š” children์œผ๋กœ <HeadingNumber /> ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค.

- <Hello />๋Š” count ์ƒํƒœ๋ฅผ ๊ฐ€์ง€์ง€๋งŒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

count ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด <Counter Provider />์™€ useCounter()๋ฅผ ์‚ฌ์šฉํ•œ <Heading Number />, <Number />, <Hello /> ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ฆฌ๋ Œ๋”๋ง ๋œ๋‹ค. (actions๋Š” useMemo๋ฅผ ์ ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, <Plus /> <Minus /> ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฆฌ๋ Œ๋”๋ง ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ)

 

<Text />๋Š” <HeadingNumber />๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜์—ˆ์–ด๋„ context ๊ฐ’์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌ๋ Œ๋”๋ง ๋˜์ง€ ์•Š๋Š”๋‹ค.

์œ„ ๊ฒฝ์šฐ๋“ค์„ ํ†ตํ•ด Context API์˜ ๋ชจ๋“  ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋ผ๋Š” ์‚ฌ์‹ค์ด๋‹ค!

 

๐Ÿ‘€ ํ•˜๋‚˜์˜ context provider๋กœ ๊ฐ’๊ณผ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด?

const CounterContext = createContext({
  counter: 0,
  actions: { increase: () => {}, decrease: () => {} },
});

export function CounterProvider({ children }: { children: React.ReactNode }) {
  const [counter, setCounter] = useState(0);
  const actions = useMemo(
    () => ({
      increase() {
        setCounter((prev) => prev + 1);
      },
      decrease() {
        setCounter((prev) => prev - 1);
      },
    }),
    [],
  );
  const value = useMemo(() => ({ counter, actions }), [counter, actions]);
  console.log('Counter Provider');

  return (
    <CounterContext.Provider value={value}>{children}</CounterContext.Provider>
  );
}

export function useCounter() {
  const value = useContext(CounterContext);
  return value;
}
// Plus.tsx
export default function Plus() {
  const {
    actions: { increase },
  } = useCounter();
  console.log('Plus');

  return <button onClick={increase}>+</button>;
}

// Minus.tsx
export default function Minus() {
  const {
    actions: { decrease },
  } = useCounter();
  console.log('Minus');

  return <button onClick={decrease}>-</button>;
}

ํ•˜๋‚˜์˜ provider๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ์ „๋‹ฌํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ context์˜ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•œ๋‹ค. actions๋Š” useMemo๋ฅผ ์ ์šฉํ–ˆ์–ด๋„, counter๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค value์˜ useMemo dependencies๊ฐ€ ๋ฐ”๋€Œ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

๊ทธ๋ž˜์„œ ์ƒํƒœ๊ฐ€ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ฐ’๊ณผ ์ƒํƒœ ๋ณ€๊ฒฝ ํ•จ์ˆ˜ provider๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๋Š”๊ฒŒ ์ข‹๋‹ค.