TypeScript๋Š” JavaScript ์ฝ”๋“œ ๋ฒ ์ด์Šค์— ํƒ€์ž… ์ •์˜๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ TypeScript๋Š” JSX๋ฅผ ์ง€์›ํ•˜๋ฉฐ, @types/react ๋ฐ @types/react-dom์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์™„์ „ํ•œ React Web ์ง€์›์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ค์น˜

๋ชจ๋“  ํ”„๋กœ๋•์…˜ ์ˆ˜์ค€์˜ React ํ”„๋ ˆ์ž„์›Œํฌ๋Š” TypeScript ์‚ฌ์šฉ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๋ณ„ ์„ค์น˜ ๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ฅด์„ธ์š”.

๊ธฐ์กด React ํ”„๋กœ์ ํŠธ์— TypeScript ์ถ”๊ฐ€ํ•˜๊ธฐ

์ตœ์‹  ๋ฒ„์ „์˜ React ํƒ€์ž… ์ •์˜๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

ํ„ฐ๋ฏธ๋„
npm install @types/react @types/react-dom

๋‹ค์Œ ์ปดํŒŒ์ผ๋Ÿฌ ์˜ต์…˜์„ tsconfig.json์— ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. dom์€ lib์— ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(์ฃผ์˜: lib ์˜ต์…˜์ด ์ง€์ •๋˜์ง€ ์•Š์œผ๋ฉด, ๊ธฐ๋ณธ์ ์œผ๋กœ dom์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค).
  2. jsx๋ฅผ ์œ ํšจํ•œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” preserve๋กœ ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฒŒ์‹œํ•˜๋Š” ๊ฒฝ์šฐ ์–ด๋–ค ๊ฐ’์„ ์„ ํƒํ•ด์•ผ ํ•˜๋Š”์ง€ jsx ์„ค๋ช…์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

React ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋Š” TypeScript

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

JSX๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” ๋ชจ๋“  ํŒŒ์ผ์€ .tsx ํŒŒ์ผ ํ™•์žฅ์ž๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ด ํŒŒ์ผ์ด JSX๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์Œ์„ TypeScript์— ์•Œ๋ ค์ฃผ๋Š” TypeScript ์ „์šฉ ํ™•์žฅ์ž์ž…๋‹ˆ๋‹ค.

React์™€ ํ•จ๊ป˜ TypeScript๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ React์™€ ํ•จ๊ป˜ JavaScript๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋กœ ์ž‘์—…ํ•  ๋•Œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ฐจ์ด์ ์€ ์ปดํฌ๋„ŒํŠธ์˜ props์— ํƒ€์ž…์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํƒ€์ž…์€ ์—๋””ํ„ฐ์—์„œ ์ •ํ™•์„ฑ์„ ๊ฒ€์‚ฌํ•˜๊ณ  ์ธ๋ผ์ธ ๋ฌธ์„œ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋น ๋ฅด๊ฒŒ ์‹œ์ž‘ํ•˜๊ธฐ ๊ฐ€์ด๋“œ์—์„œ ๊ฐ€์ ธ์˜จ MyButton ์ปดํฌ๋„ŒํŠธ๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด ๋ฒ„ํŠผ์˜ title์„ ์„ค๋ช…ํ•˜๋Š” ํƒ€์ž…์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function MyButton({ title }: { title: string }) {
  return (
    <button>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="I'm a button" />
    </div>
  );
}

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

์ด ๋ฌธ์„œ์— ์žˆ๋Š” ์ƒŒ๋“œ๋ฐ•์Šค๋“ค์€ TypeScript ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฃฐ ์ˆ˜๋Š” ์žˆ์ง€๋งŒ ํƒ€์ž…์„ ๊ฒ€์‚ฌํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์ฆ‰, TypeScript ์ƒŒ๋“œ๋ฐ•์Šค๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ ํ•™์Šตํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ ํƒ€์ž… ์˜ค๋ฅ˜๋‚˜ ๊ฒฝ๊ณ ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ๋ฐ›์œผ๋ ค๋ฉด, TypeScript Playground๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ๋” ์™„์ „ํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ ์˜จ๋ผ์ธ ์ƒŒ๋“œ๋ฐ•์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์ธ๋ผ์ธ ๋ฌธ๋ฒ•์€ ์ปดํฌ๋„ŒํŠธ์— ํƒ€์ž…์„ ์ œ๊ณตํ•˜๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด์ง€๋งŒ, ์„ค๋ช…ํ•  ํ•„๋“œ๊ฐ€ ๋งŽ์•„์ง€๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด ๋‹ค๋ฃจ๊ธฐ ์–ด๋ ค์›Œ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€์‹ , interface๋‚˜ type์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์˜ props๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

interface MyButtonProps {
  /** ๋ฒ„ํŠผ ์•ˆ์— ๋ณด์—ฌ์งˆ ํ…์ŠคํŠธ */
  title: string;
  /** ๋ฒ„ํŠผ์ด ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€ */
  disabled: boolean;
}

function MyButton({ title, disabled }: MyButtonProps) {
  return (
    <button disabled={disabled}>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="I'm a disabled button" disabled={true}/>
    </div>
  );
}

์ปดํฌ๋„ŒํŠธ์˜ props๋ฅผ ์„ค๋ช…ํ•˜๋Š” ํƒ€์ž…์€ ์›ํ•˜๋Š” ๋งŒํผ ๋‹จ์ˆœํ•˜๊ฑฐ๋‚˜ ๋ณต์žกํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, type ๋˜๋Š” interface๋กœ ์„ค๋ช…๋˜๋Š” ๊ฐ์ฒด ํƒ€์ž…์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. TypeScript๊ฐ€ ๊ฐ์ฒด๋ฅผ ์„ค๋ช…ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๊ฐ์ฒด ํƒ€์ž…์—์„œ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ, ์œ ๋‹ˆ์–ธ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ช‡ ๊ฐ€์ง€ ํƒ€์ž… ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋  ์ˆ˜ ์žˆ๋Š” prop์„ ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ๊ณผ ๋” ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์˜ˆ์‹œ์— ๋Œ€ํ•œ ํƒ€์ž…์—์„œ ํƒ€์ž… ๋งŒ๋“ค๊ธฐ ๊ฐ€์ด๋“œ ์—ญ์‹œ ํฅ๋ฏธ๋กœ์šธ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Hooks ์˜ˆ์‹œ

@types/react์˜ ํƒ€์ž… ์ •์˜์—๋Š” ๋‚ด์žฅ Hooks์— ๋Œ€ํ•œ ํƒ€์ž…์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€ ์„ค์ • ์—†์ด ์ปดํฌ๋„ŒํŠธ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ์— ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋ฅผ ๊ณ ๋ คํ•˜๋„๋ก ๋งŒ๋“ค์–ด์กŒ๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์ถ”๋ก ๋œ ํƒ€์ž…์„ ์–ป์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด์ƒ์ ์œผ๋กœ๋Š” ํƒ€์ž…์„ ์ œ๊ณตํ•˜๋Š” ์‚ฌ์†Œํ•œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ, hooks์— ํƒ€์ž…์„ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์˜ ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์‹œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

useState

useState hook์€ ์ดˆ๊ธฐ state๋กœ ์ „๋‹ฌ๋œ ๊ฐ’์„ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ’์˜ ํƒ€์ž…์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด

// ํƒ€์ž…์„ "boolean"์œผ๋กœ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค
const [enabled, setEnabled] = useState(false);

boolean ํƒ€์ž…์ด enabled์— ํ• ๋‹น๋˜๊ณ , setEnabled ๋Š” boolean ์ธ์ˆ˜๋‚˜ boolean์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ›๋Š” ํ•จ์ˆ˜๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. state์— ๋Œ€ํ•œ ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋ ค๋ฉด useState ํ˜ธ์ถœ์— ํƒ€์ž… ์ธ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

// ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž…์„ "boolean"์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค
const [enabled, setEnabled] = useState<boolean>(false);

์ด ๊ฒฝ์šฐ์—๋Š” ๊ทธ๋‹ค์ง€ ์œ ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ, ํƒ€์ž… ์ œ๊ณต์„ ์›ํ•˜๊ฒŒ ๋˜๋Š” ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ๋Š” ์œ ๋‹ˆ์–ธ ํƒ€์ž…์ด ์žˆ๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์—ฌ๊ธฐ์„œ status๋Š” ๋ช‡ ๊ฐ€์ง€ ๋‹ค๋ฅธ ๋ฌธ์ž์—ด ์ค‘ ํ•˜๋‚˜์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

type Status = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<Status>("idle");

๋˜๋Š” State ๊ตฌ์กฐํ™” ์›์น™์—์„œ ๊ถŒ์žฅํ•˜๋Š” ๋Œ€๋กœ, ๊ด€๋ จ state๋ฅผ ๊ฐ์ฒด๋กœ ๊ทธ๋ฃนํ™”ํ•˜๊ณ  ๊ฐ์ฒด ํƒ€์ž…์„ ํ†ตํ•ด ๋‹ค๋ฅธ ๊ฐ€๋Šฅ์„ฑ์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success', data: any }
| { status: 'error', error: Error };

const [requestState, setRequestState] = useState<RequestState>({ status: 'idle' });

useReducer

useReducer Hook์€ reducer ํ•จ์ˆ˜์™€ ์ดˆ๊ธฐ state๋ฅผ ์ทจํ•˜๋Š” ๋” ๋ณต์žกํ•œ Hook์ž…๋‹ˆ๋‹ค. reducer ํ•จ์ˆ˜์˜ ํƒ€์ž…์€ ์ดˆ๊ธฐ state์—์„œ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค. state์— ๋Œ€ํ•œ ํƒ€์ž…์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด useReducer ํ˜ธ์ถœ์— ํƒ€์ž… ์ธ์ˆ˜๋ฅผ ์„ ํƒ์ ์œผ๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋Œ€์‹  ์ดˆ๊ธฐ state์—์„œ ํƒ€์ž…์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์€ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

import {useReducer} from 'react';

interface State {
   count: number
};

type CounterAction =
  | { type: "reset" }
  | { type: "setCount"; value: State["count"] }

const initialState: State = { count: 0 };

function stateReducer(state: State, action: CounterAction): State {
  switch (action.type) {
    case "reset":
      return initialState;
    case "setCount":
      return { ...state, count: action.value };
    default:
      throw new Error("Unknown action");
  }
}

export default function App() {
  const [state, dispatch] = useReducer(stateReducer, initialState);

  const addFive = () => dispatch({ type: "setCount", value: state.count + 5 });
  const reset = () => dispatch({ type: "reset" });

  return (
    <div>
      <h1>Welcome to my counter</h1>

      <p>Count: {state.count}</p>
      <button onClick={addFive}>Add 5</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

๋ช‡ ๊ฐ€์ง€ ์ฃผ์š” ์œ„์น˜์—์„œ TypeScript๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • interface State๋Š” reducer state์˜ ๋ชจ์–‘์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
  • type CounterAction์€ reducer์— dispatch ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ์•ก์…˜์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
  • const initialState: State๋Š” ์ดˆ๊ธฐ state์˜ ํƒ€์ž…์„ ์ œ๊ณตํ•˜๊ณ , ๊ธฐ๋ณธ์ ์œผ๋กœ useReducer์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํƒ€์ž…๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • stateReducer(state: State, action: CounterAction): State๋Š” reducer ํ•จ์ˆ˜์˜ ์ธ์ˆ˜์™€ ๋ฐ˜ํ™˜ ๊ฐ’์˜ ํƒ€์ž…์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

initialState์— ํƒ€์ž…์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๋ช…์‹œ์ ์ธ ๋Œ€์•ˆ์€ useReducer์— ํƒ€์ž… ์ธ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

import { stateReducer, State } from './your-reducer-implementation';

const initialState = { count: 0 };

export default function App() {
const [state, dispatch] = useReducer<State>(stateReducer, initialState);
}

useContext

useContext Hook์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด props๋ฅผ ์ „๋‹ฌํ•  ํ•„์š” ์—†์ด ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค. Provider ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋ฉฐ, ์ข…์ข… ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ’์„ ์†Œ๋น„ํ•˜๋Š” Hook์„ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

context์—์„œ ์ œ๊ณตํ•œ ๊ฐ’์˜ ํƒ€์ž…์€ createContext ํ˜ธ์ถœ์— ์ „๋‹ฌ๋œ ๊ฐ’์—์„œ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค.

import { createContext, useContext, useState } from 'react';

type Theme = "light" | "dark" | "system";
const ThemeContext = createContext<Theme>("system");

const useGetTheme = () => useContext(ThemeContext);

export default function MyApp() {
  const [theme, setTheme] = useState<Theme>('light');

  return (
    <ThemeContext value={theme}>
      <MyComponent />
    </ThemeContext>
  )
}

function MyComponent() {
  const theme = useGetTheme();

  return (
    <div>
      <p>Current theme: {theme}</p>
    </div>
  )
}

์ด ๊ธฐ์ˆ ์€ ํ•ฉ๋ฆฌ์ ์ธ ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ์„ ๋•Œ ํšจ๊ณผ์ ์ด์ง€๋งŒ ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ๋„ ๊ฐ„ํ˜น ์žˆ์œผ๋ฉฐ, ๊ทธ๋Ÿฌํ•œ ๊ฒฝ์šฐ null์ด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํ•ฉ๋ฆฌ์ ์ด๋ผ๊ณ  ๋А๋‚„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ํƒ€์ž… ์‹œ์Šคํ…œ์ด ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ ค๋ฉด createContext์—์„œ ContextShape | null์„ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด์— ๋”ฐ๋ผ context ์†Œ๋น„์ž์— ๋Œ€ํ•œ ํƒ€์ž…์—์„œ | null์„ ์ œ๊ฑฐํ•ด์•ผ ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ถŒ์žฅ ์‚ฌํ•ญ์€ Hook์ด ๋Ÿฐํƒ€์ž„์— ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ  ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ throw ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

import { createContext, useContext, useState, useMemo } from 'react';

// ์ด๊ฒƒ์€ ๋” ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ์ด์ง€๋งŒ, ๋” ๋ณต์žกํ•œ ๊ฐ์ฒด๋ฅผ ์ƒ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
type ComplexObject = {
kind: string
};

// context๋Š” ๊ธฐ๋ณธ๊ฐ’์„ ์ •ํ™•ํ•˜๊ฒŒ ๋ฐ˜์˜ํ•˜๊ธฐ ์œ„ํ•ด ํƒ€์ž…์— `| null`์„ ์‚ฌ์šฉํ•˜์—ฌ ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค.
const Context = createContext<ComplexObject | null>(null);

// Hook์˜ ๊ฒ€์‚ฌ๋ฅผ ํ†ตํ•ด `| null`์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
const useGetComplexObject = () => {
const object = useContext(Context);
if (!object) { throw new Error("useGetComplexObject must be used within a Provider") }
return object;
}

export default function MyApp() {
const object = useMemo(() => ({ kind: "complex" }), []);

return (
<Context value={object}>
<MyComponent />
</Context>
)
}

function MyComponent() {
const object = useGetComplexObject();

return (
<div>
<p>Current object: {object.kind}</p>
</div>
)
}

useMemo

useMemo Hooks๋Š” ํ•จ์ˆ˜ ํ˜ธ์ถœ๋กœ๋ถ€ํ„ฐ memorized ๋œ ๊ฐ’์„ ์ƒ์„ฑ/์žฌ์ ‘๊ทผํ•˜์—ฌ, ๋‘ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ๋œ ์ข…์†์„ฑ์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. Hook์„ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ๋Š” ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜์— ์žˆ๋Š” ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ๊ฐ’์—์„œ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค. Hook์— ํƒ€์ž… ์ธ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๋”์šฑ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// visibleTodos์˜ ํƒ€์ž…์€ filterTodos์˜ ๋ฐ˜ํ™˜ ๊ฐ’์—์„œ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค.
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);

useCallback

useCallback๋Š” ๋‘ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ๋˜๋Š” ์ข…์†์„ฑ์ด ๊ฐ™๋‹ค๋ฉด ํ•จ์ˆ˜์— ๋Œ€ํ•œ ์•ˆ์ •์ ์ธ ์ฐธ์กฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. useMemo์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ํ•จ์ˆ˜์˜ ํƒ€์ž…์€ ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜์— ์žˆ๋Š” ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ๊ฐ’์—์„œ ์ถ”๋ก ๋˜๋ฉฐ, Hook์— ํƒ€์ž… ์ธ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๋”์šฑ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const handleClick = useCallback(() => {
// ...
}, [todos]);

TypeScript strict mode์—์„œ ์ž‘์—…ํ•  ๋•Œ useCallback์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ฝœ๋ฐฑ์— ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์œ„ํ•œ ํƒ€์ž…์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฝœ๋ฐฑ์˜ ํƒ€์ž…์€ ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ๊ฐ’์—์„œ ์ถ”๋ก ๋˜๊ณ , ๋งค๊ฐœ๋ณ€์ˆ˜ ์—†์ด๋Š” ํƒ€์ž…์„ ์™„์ „ํžˆ ์ดํ•ดํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ฝ”๋“œ ์Šคํƒ€์ผ ์„ ํ˜ธ๋„์— ๋”ฐ๋ผ, ์ฝœ๋ฐฑ์„ ์ •์˜ํ•˜๋Š” ๋™์‹œ์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์˜ ํƒ€์ž…์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด React ํƒ€์ž…์˜ *EventHandler ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState, useCallback } from 'react';

export default function Form() {
const [value, setValue] = useState("Change me");

const handleChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
setValue(event.currentTarget.value);
}, [setValue])

return (
<>
<input value={value} onChange={handleChange} />
<p>Value: {value}</p>
</>
);
}

์œ ์šฉํ•œ ํƒ€์ž…๋“ค

@types/react package์—๋Š” ์ƒ๋‹นํžˆ ๊ด‘๋ฒ”์œ„ํ•œ ํƒ€์ž… ์ง‘ํ•ฉ์ด ์žˆ์œผ๋ฉฐ, React์™€ TypeScript๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ฐฉ์‹์— ์ต์ˆ™ํ•˜๋‹ค๋ฉด ์ฝ์–ด๋ณผ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. DefinitelyTyped์— ์žˆ๋Š” React ํด๋”์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ๋Š” ์ข€ ๋” ์ผ๋ฐ˜์ ์ธ ํƒ€์ž… ๋ช‡ ๊ฐ€์ง€๋ฅผ ๋‹ค๋ฃจ๊ฒ ์Šต๋‹ˆ๋‹ค.

DOM ์ด๋ฒคํŠธ

React์—์„œ DOM ์ด๋ฒคํŠธ๋กœ ์ž‘์—…ํ•  ๋•Œ, ์ข…์ข… ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋กœ๋ถ€ํ„ฐ ์ด๋ฒคํŠธ์˜ ํƒ€์ž…์„ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌํ•  ํ•จ์ˆ˜๋ฅผ ์ถ”์ถœํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋Š” ์ด๋ฒคํŠธ ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

import { useState } from 'react';

export default function Form() {
  const [value, setValue] = useState("Change me");

  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    setValue(event.currentTarget.value);
  }

  return (
    <>
      <input value={value} onChange={handleChange} />
      <p>Value: {value}</p>
    </>
  );
}

React ํƒ€์ž…์—๋Š” ์ด๋ฒคํŠธ์˜ ๋งŽ์€ ํƒ€์ž…์ด ์žˆ์œผ๋ฉฐ, ์ „์ฒด ๋ชฉ๋ก์€ DOM์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฐพ๊ณ  ์žˆ๋Š” ํƒ€์ž…์„ ๊ฒฐ์ •ํ•  ๋•Œ ๋จผ์ € ์‚ฌ์šฉ ์ค‘์ธ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์˜ ํ˜ธ๋ฒ„ ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๋ฉด, ์ด๋ฒคํŠธ์˜ ํƒ€์ž…์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์ด ๋ชฉ๋ก์— ํฌํ•จ๋˜์ง€ ์•Š์€ ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ๋ชจ๋“  ์ด๋ฒคํŠธ์˜ ๊ธฐ๋ณธ ํƒ€์ž…์ธ React.SyntheticEvent ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Children

์ปดํฌ๋„ŒํŠธ์˜ ์ž์‹์„ ์„ค๋ช…ํ•˜๋Š” ๋ฐ๋Š” ๋‘ ๊ฐ€์ง€ ์ผ๋ฐ˜์ ์ธ ๊ฒฝ๋กœ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋Š” JSX์—์„œ ์ž์‹์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ ํƒ€์ž…์˜ ์กฐํ•ฉ(union)์ธ React.ReactNode ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

interface ModalRendererProps {
title: string;
children: React.ReactNode;
}

์ด๊ฒƒ์€ ์ž์‹์— ๋Œ€ํ•ด ๋งค์šฐ ๊ด‘๋ฒ”์œ„ํ•œ ์ •์˜์ž…๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ๋Š” string์ด๋‚˜ number ๊ฐ™์€ JavaScript ์›์‹œ ๊ฐ’(primitive)์ด ์•„๋‹Œ JSX ์—˜๋ฆฌ๋จผํŠธ๋งŒ ์žˆ๋Š” React.ReactElement ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

interface ModalRendererProps {
title: string;
children: React.ReactElement;
}

์ž์‹์ด ํŠน์ • JSX ์—˜๋ฆฌ๋จผํŠธ ํƒ€์ž…์ด๋ผ๊ณ  ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด TypeScript๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, <li> ์ž์‹๋งŒ ํ—ˆ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด ํƒ€์ž… ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ ์— ์ฃผ์˜ํ•˜์„ธ์š”.

TypeScript ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ์—์„œ ํƒ€์ž… ์ฒด์ปค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ React.ReactNode์™€ React.ReactElement์˜ ๋ชจ๋“  ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Style Props

React์˜ ์ธ๋ผ์ธ ์Šคํƒ€์ผ์„ ์‚ฌ์šฉํ•  ๋•Œ, React.CSSProperties๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ style prop์— ์ „๋‹ฌ๋œ ๊ฐ์ฒด๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํƒ€์ž…์€ ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ CSS ํ”„๋กœํผํ‹ฐ์˜ ์กฐํ•ฉ์ด๊ณ , style prop์— ์œ ํšจํ•œ CSS ํ”„๋กœํผํ‹ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์—๋””ํ„ฐ์—์„œ ์ž๋™ ์™„์„ฑ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

interface MyComponentProps {
style: React.CSSProperties;
}

์ถ”๊ฐ€ ํ•™์Šต

์ด ๊ฐ€์ด๋“œ์—์„œ React์—์„œ TypeScript๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ ์‚ฌํ•ญ์„ ๋‹ค๋ฃจ์—ˆ์ง€๋งŒ, ๋ฐฐ์šธ ๊ฒƒ์ด ๋” ๋งŽ์Šต๋‹ˆ๋‹ค. ๋ฌธ์„œ์˜ ๊ฐœ๋ณ„ API ํŽ˜์ด์ง€์—๋Š” TypeScript์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์ด ํฌํ•จ๋˜์–ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.

  • TypeScript ํ•ธ๋“œ๋ถ์€ TypeScript์— ๋Œ€ํ•œ ๊ณต์‹ ๋ฌธ์„œ๋กœ, ๋Œ€๋ถ€๋ถ„ ์ฃผ์š” ์–ธ์–ด ๊ธฐ๋Šฅ์„ ๋‹ค๋ฃจ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • TypeScript ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ์—์„œ๋Š” ๊ฐ๊ฐ์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

  • React TypeScript ์น˜ํŠธ์‹œํŠธ๋Š” React์™€ ํ•จ๊ป˜ TypeScript๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ์น˜ํŠธ์‹œํŠธ๋กœ, ์œ ์šฉํ•œ ์—ฃ์ง€ ์ผ€์ด์Šค๋ฅผ ๋งŽ์ด ๋‹ค๋ฃจ๊ณ  ์ด ๋ฌธ์„œ๋ณด๋‹ค ๋” ํญ๋„“์€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • TypeScript ์ปค๋ฎค๋‹ˆํ‹ฐ ๋””์Šค์ฝ”๋“œ๋Š” TypeScript ๋ฐ React ๋ฌธ์ œ์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•˜๊ณ  ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ๊ณณ์ž…๋‹ˆ๋‹ค.