๐Ÿ”ฅ ๋ฆฌ์•กํŠธ ๋ฆฌํŒฉํ† ๋ง[๋””์ž์ธ ํŒจํ„ด ์ ์šฉ]

๊ด€ํƒœยท2025๋…„ 10์›” 2์ผ

๋ชจ๋‹ฅ๋ถˆ

๋ชฉ๋ก ๋ณด๊ธฐ
1/7
post-thumbnail

React๋‚˜ Next.js๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค ๋ณด๋ฉด ์ฒ˜์Œ์—” ๋‹จ์ˆœํ–ˆ๋˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ด๋А์ƒˆ 1,000์ค„์ด ๋„˜๊ณ  ์†๋Œ€๋ฉด ๋‹ค๋ฅธ ๋ถ€๋ถ„์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ŠคํŒŒ๊ฒŒํ‹ฐ ์ฝ”๋“œ๋กœ ๋ณ€ํ•ด๋ฒ„๋ฆฌ๊ณค ํ•ด์š”.

๋‹ค๋“ค ์ด๋Ÿฐ ๊ฒฝํ—˜ ์žˆ์œผ์‹ ๊ฐ€์š”?

  • props๋ฅผ 5๋‹จ๊ณ„ ์ด์ƒ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ
  • ์–ด๋””๋ถ€ํ„ฐ ์ˆ˜์ •ํ•ด์•ผ ํ• ์ง€ ๋ง‰๋ง‰ํ•œ ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ
  • ๊ฐ™์€ ๋กœ์ง์ด ์—ฌ๋Ÿฌ ๊ณณ์— ๋ณต๋ถ™๋˜์–ด ์žˆ๋Š” ์ฝ”๋“œ๋ฒ ์ด์Šค

์ด๋Ÿฐ ๋ฌธ์ œ๋Š” ์šฐ๋ฆฌ๋งŒ์˜ ๊ณ ๋ฏผ์ด ์•„๋‹ˆ๋ผ ์ˆ˜๋งŽ์€ ์„ ๋ฐฐ ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ฒช์–ด์˜จ ๊ณตํ†ต์ ์ธ ๋ฌธ์ œ์˜ˆ์š”. ๊ทธ๋ฆฌ๊ณ  ์˜ค๋žœ ๊ณ ๋ฏผ ๋์— ๋งŒ๋“ค์–ด์ง„ ํ•ด๊ฒฐ์ฑ… ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ๋””์ž์ธ ํŒจํ„ด(Design Pattern) ์ด์—์š”.

ํŒจํ„ด๋งˆ๋‹ค ์–ด๋–ค ๋ฌธ์ œ๋ฅผ ํ’€๊ธฐ ์œ„ํ•ด ๋‚˜์™”๋Š”์ง€๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๋ด์ฃผ์„ธ์š”. ๋ฌด์กฐ๊ฑด ์“ฐ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ "์ด ์ƒํ™ฉ์ด๋ฉด ์ด ํŒจํ„ด"์ด๋ผ๋Š” ํŒ๋‹จ ๊ธฐ์ค€์„ ๊ฐ€์ ธ๊ฐ€๋Š” ๊ฒŒ ๋ชฉํ‘œ์˜ˆ์š”.

1. Container-Presentation ํŒจํ„ด

์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก "์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ญ˜ ๋ณด์—ฌ์ฃผ๋Š” ๊ฑด์ง€" "๋ญ˜ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฑด์ง€"๊ฐ€ ๋’ค์„ž์ด๊ธฐ ์‹œ์ž‘ํ•ด์š”. UI๋ฅผ ๋ฐ”๊พธ๋ ค๋Š”๋ฐ ๋กœ์ง๋„ ๊ฑด๋“œ๋ ค์•ผ ํ•˜๊ณ  ๋ฐ˜๋Œ€๋กœ ๋กœ์ง๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋˜๋Š” ์ƒํ™ฉ์—์„œ UI ์ฝ”๋“œ๋ฅผ ๊ฐ™์ด ์ฝ์–ด์•ผ ํ•˜๋Š” ์‹์ด์—์š”. Container-Presentation ํŒจํ„ด์€ ์ด ๋‘ ์—ญํ• ์„ ํŒŒ์ผ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌํ•ด์š”.

๋ฌธ์ œ์ƒํ™ฉ

UI์™€ ๋กœ์ง์ด ํ•œ ํŒŒ์ผ์— ๋’ค์„ž์—ฌ ๋‹จ์ผ ์ฑ…์ž„ ์›์น™์ด ๋ฌด๋„ˆ์ ธ์š”.

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

  • Container: ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋‹ด๋‹น, ๊ฐ์ข… ํ›… adapter ์—ญํ• 
  • Presentation: UI ํ‘œํ˜„๋งŒ ๋‹ด๋‹น
// โœ… Container
export default function TodoContainer() {
  const { text, setText, todos, addTodo } = useTodo();
  return (
    <TodoPresentation
      text={text}
      onTextChange={setText}
      todos={todos}
      onAdd={addTodo}
    />
  );
}

// โœ… Presentation
interface TodoPresentationProps {
  text: string;
  onTextChange: (text: string) => void;
  todos: Array<{ id: string; text: string }>;
  onAdd: () => void;
}

function TodoPresentation({ text, onTextChange, todos, onAdd }: TodoPresentationProps) {
  return (
    <>
      <input value={text} onChange={(e) => onTextChange(e.target.value)} />
      <button onClick={onAdd}>์ถ”๊ฐ€</button>
      <ul>
        {todos.map((t) => (
          <li key={t.id}>{t.text}</li>
        ))}
      </ul>
    </>
  );
}

์š”์ฆ˜์€ Custom Hook์ด Container ์—ญํ• ์„ ๋Œ€์‹ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์š”. useTodo ๊ฐ™์€ ํ›…์ด ๋ฐ์ดํ„ฐ ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๊ณ  Presentation ์ปดํฌ๋„ŒํŠธ๋Š” ์ˆœ์ˆ˜ํ•˜๊ฒŒ UI๋งŒ ํ‘œํ˜„ํ•˜๋Š” ์‹์ด์—์š”. Dan Abramov๋„ 2019๋…„์— "Custom Hook์ด ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ๋” ์ž˜ ํ•ด๊ฒฐํ•œ๋‹ค"๊ณ  ์–ธ๊ธ‰ํ–ˆ์„ ๋งŒํผ ํŒจํ„ด ์ž์ฒด์˜ ํ•„์š”์„ฑ์€ ์ค„์—ˆ์ง€๋งŒ UI์™€ ๋กœ์ง์„ ๋‚˜๋ˆˆ๋‹ค๋Š” ๊ฐœ๋…์€ ์—ฌ์ „ํžˆ ์œ ํšจํ•ด์š”.

์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ?

  • ๊ฐ™์€ UI๋ฅผ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋กœ ์žฌ์‚ฌ์šฉํ•ด์•ผ ํ•  ๋•Œ
  • ์Šคํ† ๋ฆฌ๋ถ ๋“ฑ์œผ๋กœ UI๋งŒ ๋…๋ฆฝ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์„ ๋•Œ
  • ๋””์ž์ด๋„ˆ์™€ ํ˜‘์—… ์‹œ UI๋ฅผ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ

UI์™€ ๋กœ์ง์„ ๋‚˜๋ˆ ๋†“์œผ๋ฉด ๊ฐ€๋…์„ฑ๊ณผ ์žฌ์‚ฌ์šฉ์„ฑ์ด ์˜ฌ๋ผ๊ฐ€๋Š” ๊ฑด ์‚ฌ์‹ค์ด์—์š”. ๋‹ค๋งŒ ์ž‘์€ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์˜คํžˆ๋ ค ํŒŒ์ผ์ด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋Š˜์–ด๋‚˜๋Š” ๋А๋‚Œ์ด ๋“ค ์ˆ˜ ์žˆ์–ด์š”. ํŒ€์›๋ผ๋ฆฌ "์ด ์ •๋„ ๊ทœ๋ชจ๋ฉด ๊ตณ์ด?"๋ผ๋Š” ๋Œ€ํ™”๋ฅผ ํ•œ ๋ฒˆ์ฏค ๋‚˜๋ˆ ๋ณด๋Š” ๊ฒŒ ์ข‹์•„์š”.

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ์„œ ํ•œ ๊ฐ€์ง€ ์˜๋ฌธ์ด ์ƒ๊ฒจ์š”. UI์™€ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฑด ์ข‹์€๋ฐ ๊ทธ ๋กœ์ง ์ž์ฒด๋ฅผ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”? ์˜ˆ๋ฅผ ๋“ค์–ด ์ธ์ฆ ์ฒดํฌ ๊ฐ™์€ ๊ณตํ†ต ๊ธฐ๋Šฅ์„ ์—ด ๊ตฐ๋ฐ ์ปดํฌ๋„ŒํŠธ์— ๋ถ™์—ฌ์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด์š”.

2. HOC (Higher-Order Component) ํŒจํ„ด

๋ฌธ์ œ์ƒํ™ฉ

ํŠน์ • ๊ธฐ๋Šฅ์„ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์— ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ ๋ถˆํ•„์š”ํ•œ ๋ฐ˜๋ณต ์ฝ”๋“œ๊ฐ€ ์ƒ๊ฒจ์š”.

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ๋Š” ๊ณ ์ฐจ ํ•จ์ˆ˜(HOC)๋กœ ๊ธฐ๋Šฅ์„ ์ฃผ์ž…ํ•ด์š”.

const checkAuthorization = () => {
  // ๊ถŒํ•œ์„ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•ด ์›๊ฒฉ ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์š”.
};

const withAuthorization = <P extends object>(
  Component: React.ComponentType<P>
) => {
  return function WithAuthorization(props: P) {
    const isAuthorized = checkAuthorization();
    return isAuthorized ? <Component {...props} /> : <Login />;
  };
};

์‹ค์ œ๋กœ ์ ์šฉํ•  ๋•Œ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ HOC๋กœ ๊ฐ์‹ผ ๊ฒฐ๊ณผ๋ฅผ ์ƒˆ ๋ณ€์ˆ˜์— ๋‹ด์•„์„œ ์จ์š”.

// โœ… ์ ์šฉ ์˜ˆ์‹œ
function Dashboard({ title }: { title: string }) {
  return <h1>{title}</h1>;
}

// withAuthorization์œผ๋กœ ๊ฐ์‹ธ๋ฉด ๊ถŒํ•œ ์ฒดํฌ๊ฐ€ ์ž๋™์œผ๋กœ ๋ถ™์–ด์š”.
const ProtectedDashboard = withAuthorization(Dashboard);

// ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์›๋ณธ ์ปดํฌ๋„ŒํŠธ์™€ ๋™์ผํ•œ props๋ฅผ ๊ทธ๋Œ€๋กœ ๋„˜๊ฒจ์š”.
<ProtectedDashboard title="๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€" />

์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ?

  • ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์— ๋™์ผํ•œ ์ธ์ฆ/๊ถŒํ•œ ๋กœ์ง์„ ์ ์šฉํ•  ๋•Œ
  • ๋กœ๋”ฉ/์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ฐ™์€ ๊ณตํ†ต UI ๋ž˜ํผ๊ฐ€ ํ•„์š”ํ•  ๋•Œ

์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ์ธ์ฆ ๋กœ์ง์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋ฐ˜๋ฉด HOC๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ์ค‘์ฒฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๊นŠ์–ด์ง€๊ณ  props ์ด๋ฆ„์ด ์ถฉ๋Œํ•  ๊ฐ€๋Šฅ์„ฑ๋„ ์žˆ์–ด์š”. ์š”์ฆ˜์€ Custom Hook์ด ์ƒ๋‹น ๋ถ€๋ถ„์„ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ƒˆ ํ”„๋กœ์ ํŠธ๋ผ๋ฉด HOC๋ถ€ํ„ฐ ๊บผ๋‚ด๋“œ๋Š” ๊ฒฝ์šฐ๋Š” ๋งŽ์ง€ ์•Š์€ ํŽธ์ด์—์š”.

HOC๊ฐ€ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„์˜ ์žฌ์‚ฌ์šฉ์„ ํ•ด๊ฒฐํ•ด์ค€๋‹ค๋ฉด ์ƒํƒœ ๊ด€๋ฆฌ ์ž์ฒด๊ฐ€ ๋ณต์žกํ•ด์ง€๋Š” ๋ฌธ์ œ๋Š” ๋ณ„๊ฐœ์˜ˆ์š”. useState๊ฐ€ ๋‹ค์„ฏ ๊ฐœ์ฏค ์Œ“์ด๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด ์–ด๋””์„œ ๋ฌด์—‡์ด ๋ฐ”๋€Œ๋Š”์ง€ ์ถ”์ ํ•˜๊ธฐ๊ฐ€ ์ ์  ์–ด๋ ค์›Œ์ ธ์š”.

3. Reducer ํŒจํ„ด

์ƒํƒœ๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด useState๋งŒ์œผ๋กœ๋Š” ํ•œ๊ณ„๊ฐ€ ๋А๊ปด์ ธ์š”. ํŠนํžˆ ์—ฌ๋Ÿฌ ์ƒํƒœ๊ฐ€ ์„œ๋กœ ์–ฝํ˜€ ์žˆ์„ ๋•Œ ์–ด๋–ค ์ด๋ฒคํŠธ๊ฐ€ ์–ด๋–ค ์ƒํƒœ๋ฅผ ๋ฐ”๊พธ๋Š”์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต๊ณ , ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ํƒ€์ด๋ฐ์— ์ƒํƒœ๊ฐ€ ํ‹€์–ด์ง€๊ธฐ๋„ ํ•ด์š”.

useReducer๋ฅผ ์“ฐ๋ฉด "์–ด๋–ค ์ผ์ด ์ผ์–ด๋‚ฌ๋Š”๊ฐ€(action)"์™€ "๊ทธ ๊ฒฐ๊ณผ ์ƒํƒœ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋‹ฌ๋ผ์ง€๋Š”๊ฐ€(reducer)"๋ฅผ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”.

interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

type Action =
  | { type: "ADD"; text: string }
  | { type: "TOGGLE"; id: string }
  | { type: "DELETE"; id: string };

function todoReducer(state: Todo[], action: Action): Todo[] {
  switch (action.type) {
    case "ADD":
      return [
        ...state,
        {
          id: crypto.randomUUID(),
          text: action.text,
          completed: false,
        },
      ];
    case "TOGGLE":
      return state.map((t) =>
        t.id === action.id ? { ...t, completed: !t.completed } : t
      );
    case "DELETE":
      return state.filter((t) => t.id !== action.id);
    default:
      const _exhaustiveCheck: never = action;
      throw new Error(`Unknown action type`);
  }
}

// โœ… ์‚ฌ์šฉ ์˜ˆ์‹œ
const [todos, dispatch] = useReducer(todoReducer, []);
dispatch({ type: "ADD", text: "์ƒˆ ํ•  ์ผ" });

์˜ˆ๋ฅผ ๋“ค์–ด Action ํƒ€์ž…์— { type: "CLEAR" }๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋Š”๋ฐ case "CLEAR": ๋ถ„๊ธฐ๋ฅผ ๋น ๋œจ๋ฆฌ๋ฉด default์—์„œ action์ด never๊ฐ€ ์•„๋‹Œ ํƒ€์ž…์œผ๋กœ ๋‚จ์•„ TypeScript ์˜ค๋ฅ˜๊ฐ€ ๋ฐ”๋กœ ๋ฐœ์ƒํ•ด์š”. ๋†“์นœ ์•ก์…˜์ด ๋Ÿฐํƒ€์ž„์—์„œ์•ผ ํ„ฐ์ง€๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์—๋””ํ„ฐ์—์„œ ๋ฐ”๋กœ ์žกํžˆ๋Š” ๊ฑฐ์˜ˆ์š”.

์ƒํƒœ ๊ด€๋ฆฌ ๋กœ์ง์„ reducer ํ•œ ๊ณณ์— ๋ชจ์•„๋‘๋ฉด "์ด ์ƒํƒœ๊ฐ€ ์™œ ๋ฐ”๋€Œ์—ˆ์ง€?"๋ผ๋Š” ์งˆ๋ฌธ์— ๋น ๋ฅด๊ฒŒ ๋‹ตํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋‹ค๋งŒ ์ƒํƒœ๊ฐ€ ๋‹จ์ˆœํ•œ ๊ฒฝ์šฐ์—๋Š” ์˜คํžˆ๋ ค ์ฝ”๋“œ๊ฐ€ ์žฅํ™ฉํ•ด์ง€๋‹ˆ๊นŒ useState ๋‘์„ธ ๊ฐœ๋กœ ์ถฉ๋ถ„ํ•œ ์ƒํ™ฉ์—์„œ ๊ตณ์ด ๊บผ๋‚ผ ํ•„์š”๋Š” ์—†์–ด์š”.

์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ์ •๋ฆฌ๋๋‹ค๋ฉด ์ด์ œ ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ์ƒํƒœ ๊ณต์œ  ๋ฌธ์ œ๋กœ ๋„˜์–ด๊ฐ€๋ณผ๊ฒŒ์š”. props๋ฅผ ๊นŠ์ด ์ „๋‹ฌํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ์—ฌ๋Ÿฌ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฐ™์€ ์ƒํƒœ๋ฅผ ์“ฐ๊ฒŒ ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”?

4. Compound Components ํŒจํ„ด

๋ฌธ์ œ์ƒํ™ฉ

Props drilling์ด ์‹ฌํ•ด์ง€๊ณ  ์˜ต์…˜์ด ๋งŽ์•„์งˆ์ˆ˜๋ก ์ปดํฌ๋„ŒํŠธ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๋ณต์žกํ•ด์ ธ์š”.

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์ด Context๋ฅผ ํ†ตํ•ด ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ฒŒ ํ•ด์š”.

interface ToggleContextType {
  on: boolean;
  toggle: () => void;
}

const ToggleContext = createContext<ToggleContextType | null>(null);

function useToggleContext() {
  const context = useContext(ToggleContext);
  if (!context) throw new Error('Toggle ๋‚ด๋ถ€์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.');
  return context;
}

function Toggle({ children }: { children: React.ReactNode }) {
  const [on, setOn] = useState(false);

  // toggle์„ useCallback์œผ๋กœ ์•ˆ์ •ํ™”ํ•˜๊ณ  value๋ฅผ useMemo๋กœ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•ด์š”.
  // Context value๊ฐ€ ๋งค ๋ Œ๋”๋งˆ๋‹ค ์ƒˆ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์ง€๋ฉด
  // ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ ์ „์ฒด๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋ฆฌ๋ Œ๋”๋  ์ˆ˜ ์žˆ๊ฑฐ๋“ ์š”.
  const toggle = useCallback(() => setOn((o) => !o), []);
  const value = useMemo(() => ({ on, toggle }), [on, toggle]);

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

// Toggle.Button์ฒ˜๋Ÿผ ์ปดํฌ๋„ŒํŠธ์— ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ”„๋กœํผํ‹ฐ๋กœ ๋ถ™์—ฌ์š”.
// ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Toggle๊ณผ ๊ด€๋ จ๋œ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ Toggle ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์•ˆ์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ 
// ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ๋„ "์ด ์ปดํฌ๋„ŒํŠธ๋Š” Toggle ์•ˆ์—์„œ ์จ์•ผ ํ•˜๋Š” ๊ฒƒ"์ž„์„ ์ด๋ฆ„๋งŒ ๋ณด๊ณ  ์•Œ ์ˆ˜ ์žˆ์–ด์š”.
Toggle.Button = function ToggleButton() {
  const { on, toggle } = useToggleContext();
  return <button onClick={toggle}>{on ? "ON" : "OFF"}</button>;
};

Toggle.Message = function ToggleMessage({ children }: { children: React.ReactNode }) {
  const { on } = useToggleContext();
  return on ? <span>{children}</span> : null;
};

// โœ… ์‚ฌ์šฉ ์˜ˆ์‹œ
<Toggle>
  <Toggle.Button />
  <Toggle.Message>ํ† ๊ธ€์ด ์ผœ์กŒ์Šต๋‹ˆ๋‹ค!</Toggle.Message>
</Toggle>

์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ?

  • ์—ฌ๋Ÿฌ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•ด์•ผ ํ•  ๋•Œ
  • ์กฐ๋ฆฝํ˜• UI๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์‹ถ์„ ๋•Œ
  • UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ์„ค๊ณ„ํ•  ๋•Œ

ํ™•์žฅ์„ฑ๊ณผ ๊ฐ€๋…์„ฑ ๋ฉด์—์„œ ํ™•์‹คํžˆ ์œ ์šฉํ•œ ํŒจํ„ด์ด์—์š”. props drilling ์ง€์˜ฅ์„ ํ”ผํ•˜๋ฉด์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์กฐ๋ฆฝํ•˜๋“ฏ ์“ธ ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ์š”. ์ „์—ญ ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•œ ์ˆ˜์ค€์ด ์•„๋‹ˆ๋ผ ํŠน์ • UI ๋ธ”๋ก ์•ˆ์—์„œ๋งŒ ๊ณต์œ ๋˜๋Š” ์ƒํƒœ๋ผ๋ฉด ์ด ํŒจํ„ด์ด ๋”ฑ ๋งž์•„์š”.

์ด์ฏค ๋˜๋ฉด ํ•œ ๊ฐ€์ง€ ๊ณตํ†ต์ ์ด ๋ณด์—ฌ์š”. ์•ž์„œ ์†Œ๊ฐœํ•œ ํŒจํ„ด๋“ค, HOC๋“  Compound Components๋“  ๊ฒฐ๊ตญ "๋กœ์ง์„ ์–ด๋””์— ๋‘๊ณ  ์–ด๋–ป๊ฒŒ ์žฌ์‚ฌ์šฉํ•  ๊ฒƒ์ธ๊ฐ€"๋ผ๋Š” ์งˆ๋ฌธ์—์„œ ์ถœ๋ฐœํ–ˆ์–ด์š”. ๊ทธ๋ฆฌ๊ณ  ํ˜„๋Œ€ React์—์„œ ์ด ์งˆ๋ฌธ์— ๊ฐ€์žฅ ์ง์ ‘์ ์œผ๋กœ ๋‹ตํ•˜๋Š” ์ˆ˜๋‹จ์ด ๋ฐ”๋กœ Custom Hook์ด์—์š”.

5. Custom Hook ํŒจํ„ด

์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋˜‘๊ฐ™์€ ํผ ๊ด€๋ฆฌ ์ฝ”๋“œ๋ฅผ ๋ณต๋ถ™ํ•˜๋‹ค๊ฐ€ ์ง€์นœ ์  ์žˆ์–ด์š”? before/after๋ฅผ ๋ณด๋ฉด ์ด๊ฒŒ ์™œ ํ•„์š”ํ•œ์ง€ ๋ฐ”๋กœ ๋А๊ปด์ ธ์š”.

// โŒ Before: ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ๋ฐ˜๋ณต๋ผ์š”.
function LoginForm() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  return (
    <form>
      <input value={username} onChange={(e) => setUsername(e.target.value)} />
      <input value={password} onChange={(e) => setPassword(e.target.value)} />
    </form>
  );
}
// โœ… After: ๋ฐ˜๋ณต ๋กœ์ง์„ ํ›…์œผ๋กœ ๋ฝ‘์•„๋‚ด๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊น”๋”ํ•ด์ ธ์š”.
function useFormInput(initial = "") {
  const [value, setValue] = useState(initial);
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };
  const reset = () => setValue(initial);

  // inputProps๋งŒ ๋”ฐ๋กœ ๋ฌถ์–ด์„œ ๋ฐ˜ํ™˜ํ•ด์š”.
  // { value, onChange, reset }์„ ํ†ต์งธ๋กœ ์Šคํ”„๋ ˆ๋“œํ•˜๋ฉด
  // reset์ด DOM์— ์•Œ ์ˆ˜ ์—†๋Š” ์†์„ฑ์œผ๋กœ ์ „๋‹ฌ๋˜์–ด ์ฝ˜์†” ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋“ ์š”.
  return { inputProps: { value, onChange }, reset };
}

function LoginForm() {
  const username = useFormInput("");
  const password = useFormInput("");

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();

    await login(username.inputProps.value, password.inputProps.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input {...username.inputProps} placeholder="์•„์ด๋””" />
      <input {...password.inputProps} type="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ" />
      <button type="submit">๋กœ๊ทธ์ธ</button>
    </form>
  );
}

์ด์ „์˜ HOC๋‚˜ Render Props๊ฐ€ ๋‹ด๋‹นํ•˜๋˜ ์—ญํ•  ๋Œ€๋ถ€๋ถ„์„ Custom Hook์ด ๋Œ€์ฒดํ•˜๋ฉด์„œ ํ˜„๋Œ€ React์—์„œ ๊ฐ€์žฅ ์ž์ฃผ ์“ฐ์ด๋Š” ํŒจํ„ด์ด ๋์–ด์š”. ๋กœ์ง ์žฌ์‚ฌ์šฉ์ด๋ผ๋Š” ๋ชฉ์ ๋งŒ ๋ณด๋ฉด ๊ฑฐ์˜ ๋งŒ๋Šฅ์— ๊ฐ€๊น์ฃ . ๋‹ค๋งŒ ํ•œ๊ณ„๋„ ์žˆ์–ด์š”. ๋กœ์ง์€ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ ์‚ฌ์ด์˜ UI ์ƒํƒœ ๊ณต์œ ๋Š” ์—ฌ์ „ํžˆ ๋ณ„๋„ ์ˆ˜๋‹จ(Context, ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)์ด ํ•„์š”ํ•ด์š”. ํ›…์„ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด renderHook ๊ฐ™์€ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์„ค์ •๋„ ๋”ฐ๋กœ ์ฑ™๊ฒจ์•ผ ํ•˜๊ณ ์š”.

์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ?

  • ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋™์ผํ•œ ๋กœ์ง์„ ์‚ฌ์šฉํ•  ๋•Œ
  • API ํ˜ธ์ถœ, ํผ ๊ด€๋ฆฌ, ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋“ฑ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋กœ์ง์ด ์žˆ์„ ๋•Œ
  • ์ปดํฌ๋„ŒํŠธ์—์„œ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•ด ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์„ ๋•Œ

๋งˆ๋ฌด๋ฆฌ

๋””์ž์ธ ํŒจํ„ด์€ ๋งŒ๋Šฅ ํ•ด๊ฒฐ์ฑ…์€ ์•„๋‹ˆ๋ผ๊ณ  ์ƒ๊ฐํ•ด์š”.
ํ•˜์ง€๋งŒ ๋ฐ˜๋ณต๋˜๋Š” ๋ฌธ์ œ์— ๋Œ€ํ•œ ๊ฒ€์ฆ๋œ ํ•ด๋ฒ•์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์–ด์š”.
React์—์„œ ์ž์ฃผ ์“ฐ์ด๋Š” ํŒจํ„ด๋“ค์„ ์ดํ•ดํ•˜๊ณ  ์ ์ ˆํ•˜๊ฒŒ ํ™œ์šฉํ•˜๋ฉด ์ฝ”๋“œ ํ’ˆ์งˆ์ด ๋ˆˆ์— ๋„๊ฒŒ ์ข‹์•„์งˆ ์ˆ˜ ์žˆ์–ด์š”.

์ฐธ๊ณ  ์ž๋ฃŒ

profile
์–ด์ œ๋ณด๋‹ค ๋‚˜์€ ์˜ค๋Š˜

0๊ฐœ์˜ ๋Œ“๊ธ€