๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

โœ๏ธ/React

React ๊ณต๋ถ€ํ•˜๊ธฐ(8) - ์ƒํƒœ ๊ด€๋ฆฌ, Context API of ๋ฆฌ์•กํŠธ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ

3. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ๋ฆฌ์•กํŠธ ์ƒํƒœ ๊ด€๋ฆฌํ•˜๊ธฐ

 

์นด์šดํ„ฐ ๋งŒ๋“ค์–ด๋ณด๊ธฐ

 

์ธํ’‹ ์ƒํƒœ ๊ด€๋ฆฌํ•˜๊ธฐ

 

useReducer ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

<src/SampleReducer.tsx>

import React, { useReducer } from 'react';

type Color = 'red' | 'orange' | 'yellow';

type State = {
  count: number;
  text: string;
  color: Color;
  isGood: boolean;
};

type Action =
  | { type: 'SET_COUNT'; count: number }
  | { type: 'SET_TEXT'; text: string }
  | { type: 'SET_COLOR'; color: Color }
  | { type: 'TOGGLE_GOOD' };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'SET_COUNT':
      return {
        ...state,
        count: action.count // count๊ฐ€ ์ž๋™์™„์„ฑ๋˜๋ฉฐ, number ํƒ€์ž…์ธ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      };
    case 'SET_TEXT':
      return {
        ...state,
        text: action.text // text๊ฐ€ ์ž๋™์™„์„ฑ๋˜๋ฉฐ, string ํƒ€์ž…์ธ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      };
    case 'SET_COLOR':
      return {
        ...state,
        color: action.color // color ๊ฐ€ ์ž๋™์™„์„ฑ๋˜๋ฉฐ color ๊ฐ€ Color ํƒ€์ž…์ธ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      };
    case 'TOGGLE_GOOD':
      return {
        ...state,
        isGood: !state.isGood
      };
    default:
      throw new Error('Unhandled action');
  }
}

function ReducerSample() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    text: 'hello',
    color: 'red',
    isGood: true
  });

  const setCount = () => dispatch({ type: 'SET_COUNT', count: 5 }); // count ๋ฅผ ๋„ฃ์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ๋ฐœ์ƒ
  const setText = () => dispatch({ type: 'SET_TEXT', text: 'bye' }); // text ๋ฅผ ๋„ฃ์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ
  const setColor = () => dispatch({ type: 'SET_COLOR', color: 'orange' }); // orange ๋ฅผ ๋„ฃ์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ
  const toggleGood = () => dispatch({ type: 'TOGGLE_GOOD' });

  return (
    <div>
      <p>
        <code>count: </code> {state.count}
      </p>
      <p>
        <code>text: </code> {state.text}
      </p>
      <p>
        <code>color: </code> {state.color}
      </p>
      <p>
        <code>isGood: </code> {state.isGood ? 'true' : 'false'}
      </p>
      <div>
        <button onClick={setCount}>SET_COUNT</button>
        <button onClick={setText}>SET_TEXT</button>
        <button onClick={setColor}>SET_COLOR</button>
        <button onClick={toggleGood}>TOGGLE_GOOD</button>
      </div>
    </div>
  );
}

export default ReducerSample;

<App.tsx>

import React from 'react';
import ReducerSample from './ReducerSample';

const App: React.FC = () => {
  return <ReducerSample />;
};

export default App;

 

 

4. TypeScript์™€ Context API ํ™œ์šฉํ•˜๊ธฐ

<src/SampleContext.tsx>

import React, { useReducer, useContext, createContext, Dispatch } from 'react';

// ํ•„์š”ํ•œ ํƒ€์ž…๋“ค์„ ๋ฏธ๋ฆฌ ์„ ์–ธ

type Color = 'red' | 'orange' | 'yellow';

// ์ƒํƒœ๋ฅผ ์œ„ํ•œ ํƒ€์ž…
type State = {
  count: number;
  text: string;
  color: Color;
  isGood: boolean;
};

// ๋ชจ๋“  ์•ก์…˜๋“ค์„ ์œ„ํ•œ ํƒ€์ž…
type Action =
  | { type: 'SET_COUNT'; count: number }
  | { type: 'SET_TEXT'; text: string }
  | { type: 'SET_COLOR'; color: Color }
  | { type: 'TOGGLE_GOOD' };

// ๋””์ŠคํŒจ์น˜๋ฅผ ์œ„ํ•œ ํƒ€์ž… (Dispatch ๋ฅผ ๋ฆฌ์•กํŠธ์—์„œ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ์Œ), ์•ก์…˜๋“ค์˜ ํƒ€์ž…์„ Dispatch ์˜ Generics๋กœ ์„ค์ •
type SampleDispatch = Dispatch<Action>;

// Context ๋งŒ๋“ค๊ธฐ
const SampleStateContext = createContext<State | null>(null);
const SampleDispatchContext = createContext<SampleDispatch | null>(null);

// ๋ฆฌ๋“€์„œ
function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'SET_COUNT':
      return {
        ...state,
        count: action.count // count๊ฐ€ ์ž๋™์™„์„ฑ๋˜๋ฉฐ, number ํƒ€์ž…์ธ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      };
    case 'SET_TEXT':
      return {
        ...state,
        text: action.text // text๊ฐ€ ์ž๋™์™„์„ฑ๋˜๋ฉฐ, string ํƒ€์ž…์ธ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      };
    case 'SET_COLOR':
      return {
        ...state,
        color: action.color // color ๊ฐ€ ์ž๋™์™„์„ฑ๋˜๋ฉฐ color ๊ฐ€ Color ํƒ€์ž…์ธ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      };
    case 'TOGGLE_GOOD':
      return {
        ...state,
        isGood: !state.isGood
      };
    default:
      throw new Error('Unhandled action');
  }
}

// SampleProvider ์—์„œ useReduer๋ฅผ ์‚ฌ์šฉํ•˜๊ณ 
// SampleStateContext.Provider ์™€ SampleDispatchContext.Provider ๋กœ children ์„ ๊ฐ์‹ธ์„œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
export function SampleProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    text: 'hello',
    color: 'red',
    isGood: true
  });

  return (
    <SampleStateContext.Provider value={state}>
      <SampleDispatchContext.Provider value={dispatch}>
        {children}
      </SampleDispatchContext.Provider>
    </SampleStateContext.Provider>
  );
}

// state ์™€ dispatch ๋ฅผ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์ปค์Šคํ…€ Hooks
export function useSampleState() {
  const state = useContext(SampleStateContext);
  if (!state) throw new Error('Cannot find SampleProvider'); // ์œ ํšจํ•˜์ง€ ์•Š์„๋• ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ
  return state;
}

export function useSampleDispatch() {
  const dispatch = useContext(SampleDispatchContext);
  if (!dispatch) throw new Error('Cannot find SampleProvider'); // ์œ ํšจํ•˜์ง€ ์•Š์„๋• ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ
  return dispatch;
}

<src/App.js>

import React from 'react';
import SampleReducer from './SampleReducer';
import { SampleProvider } from './SampleContext';

const App: React.FC = () => {
  return (
    <SampleProvider>
      <SampleReducer />
    </SampleProvider>
  );
};

export default App;

<src/SampleReducer.tsx>

import React from 'react';
import { useSampleState, useSampleDispatch } from './SampleContext';

function ReducerSample() {
  const state = useSampleState();
  const dispatch = useSampleDispatch();

  const setCount = () => dispatch({ type: 'SET_COUNT', count: 5 }); // count ๋ฅผ ๋„ฃ์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ๋ฐœ์ƒ
  const setText = () => dispatch({ type: 'SET_TEXT', text: 'bye' }); // text ๋ฅผ ๋„ฃ์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ
  const setColor = () => dispatch({ type: 'SET_COLOR', color: 'orange' }); // orange ๋ฅผ ๋„ฃ์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ
  const toggleGood = () => dispatch({ type: 'TOGGLE_GOOD' });

  return (
    <div>
      <p>
        <code>count: </code> {state.count}
      </p>
      <p>
        <code>text: </code> {state.text}
      </p>
      <p>
        <code>color: </code> {state.color}
      </p>
      <p>
        <code>isGood: </code> {state.isGood ? 'true' : 'false'}
      </p>
      <div>
        <button onClick={setCount}>SET_COUNT</button>
        <button onClick={setText}>SET_TEXT</button>
        <button onClick={setColor}>SET_COLOR</button>
        <button onClick={toggleGood}>TOGGLE_GOOD</button>
      </div>
    </div>
  );
}

export default ReducerSample;