Skip to content

typescript-cheatsheets/react-typescript-cheatsheet-kr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 

Repository files navigation

React+TypeScript Cheatsheets ํ•œ๊ตญ์–ดํŒ ๐Ÿ‡ฐ๐Ÿ‡ท

TypeScript์— ์ž…๋ฌธํ•˜๋Š” React ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ์น˜ํŠธ์‹œํŠธ(Cheetsheets)


react + ts logo

์›น ๋‹คํ๋จผํŠธ | ์˜์–ดํŒ | ํ”„๋กœ์ ํŠธ์— ๊ธฐ์—ฌํ•˜๊ธฐ | ์งˆ๋ฌธํ•˜๊ธฐ

๐Ÿ‘‹ ๋ณธ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋Š” @ryan_kim_kr์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋‹˜์ด React์™€ ํ•จ๊ป˜ TypeScript๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ณ ์ž ํ•˜์‹œ๋‹ค๋‹ˆ ์ •๋ง ๊ธฐ์œ ์†Œ์‹์ด๊ตฐ์š”! ์ž˜๋ชป๋œ ๋ถ€๋ถ„์ด ๋ฐœ๊ฒฌ๋˜์–ด ์ˆ˜์ •์ด ํ•„์š”ํ•˜๊ฑฐ๋‚˜ ๋ˆ„๋ฝ๋œ ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ๊ฐœ์„  ๋˜์–ด์•ผ ํ•  ์‚ฌํ•ญ์„ ์ด์Šˆ ๋“ฑ๋กํ•ด ์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๐Ÿ‘


All Contributors

All React + TypeScript Cheatsheets

  • ๊ธฐ์ดˆ ์น˜ํŠธ์‹œํŠธ(The Basic Cheatsheet)๋Š” React ๊ฐœ๋ฐœ์ž๊ฐ€ React app์—์„œ TS ์‚ฌ์šฉ์„ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์— ๋„์›€์„ ์ฃผ๊ธฐ ์œ„ํ•œ ๋‚ด์šฉ์ด ์ฃผ๋ฅผ ์ด๋ฃน๋‹ˆ๋‹ค.
    • ๋ชจ๋ฒ” ์‚ฌ๋ก€(Best Practices)๋ผ๊ณ  ์—ฌ๊ฒจ์ง€๋Š”, ๋ณต์‚ฌ + ๋ถ™์—ฌ๋„ฃ๊ธฐ ๊ฐ€๋Šฅํ•œ ์˜ˆ์‹œ
    • ๊ธฐ๋ณธ์ ์ธ TS Types ์‚ฌ์šฉ๋ฒ•๊ณผ ์„ค์ • ๋ฐฉ๋ฒ•
    • ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ(FAQ)์— ๋Œ€ํ•œ ๋‹ต๋ณ€
    • Generic type logic์€ ๊นŠ์ด ๋‹ค๋ฃจ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ ๋Œ€์‹ , ์ดˆ์‹ฌ์ž๋“ค์„ ์œ„ํ•ด ๊ฐ„๋‹จํ•œ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ๊ธฐ์ˆ ๋“ค์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.
    • ๊ธฐ์ดˆ ์น˜ํŠธ์‹œํŠธ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ TypeScript์— ๋Œ€ํ•ด ๋„ˆ๋ฌด ๋งŽ์€ ๊ณต๋ถ€๋ฅผ ํ•˜์ง€ ์•Š๊ณ ์„œ๋„ ์‹œ๊ฐ„ ํšจ์œจ์ ์œผ๋กœ React ๊ฐœ๋ฐœ์— TypeScript๋ฅผ ๋น ๋ฅด๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ๋ฐ ๊ทธ ๋ชฉ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ณ ๊ธ‰ ์น˜ํŠธ์‹œํŠธ(The Advanced Cheatsheet)๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ type utilities/functions/render prop/higher order copmonents ๋˜๋Š” TS+React ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•ด generic types์˜ ๊ณ ๊ธ‰ ์‚ฌ์šฉ๋ฒ•์— ๋Œ€ํ•œ ์ดํ•ด๋ฅผ ๋•์Šต๋‹ˆ๋‹ค.
    • ์ „๋ฌธ์ ์ธ ๊ฐœ๋ฐœ์ž๋“ค์„ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ํŒ๊ณผ ์š”๋ น๋“ค์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.
    • DefinitelyTyped์— ๊ธฐ์—ฌํ•˜๊ธฐ ์œ„ํ•œ ์กฐ์–ธ์„ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
    • ๊ณ ๊ธ‰ ์น˜ํŠธ์‹œํŠธ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ TypeScript๋ฅผ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ๋ฐ ๊ทธ ๋ชฉ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋งˆ์ด๊ทธ๋ ˆ์ดํŒ… ์น˜ํŠธ์‹œํŠธ(The Migrating Cheatsheet)๋Š” ๋Œ€๊ทœ๋ชจ ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ JS ๋˜๋Š” Flow์—์„œ TypsScript๋กœ ์ ์ง„์ ์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ๊ฒฝํ—˜์ž์˜ ์กฐ์–ธ์„ ์–ป๋Š”๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค.
    • ์šฐ๋ฆฌ๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๋„๋ก ์„ค๋“ํ•˜๋ ค๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ฉฐ, ์ด๋ฏธ ๊ทธ๋ ‡๊ฒŒ ํ•˜๊ณ ์ž ๊ฒฐ์ •ํ•œ ์‚ฌ๋žŒ๋“ค์„ ๋•๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.
    • โš ๏ธ ์ด ์น˜ํŠธ์‹œํŠธ๋Š” ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“ค์–ด์ง„ ์น˜ํŠธ์‹œํŠธ ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋„์›€์„ ์ฃผ๊ณ ์ž ํ•˜๋Š” ๋ชจ๋“  ๋ถ„๋“ค์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.
  • HOC ์น˜ํŠธ์‹œํŠธ(The HOC Cheatsheet)๋Š” ์˜ˆ์‹œ์™€ ํ•จ๊ป˜ HOC๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.
    • Generics์— ๋Œ€ํ•œ ์ดํ•ด๊ฐ€ ์„ ํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • โš ๏ธ ์ด ์น˜ํŠธ์‹œํŠธ๋Š” ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“ค์–ด์ง„ ์น˜ํŠธ์‹œํŠธ ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋„์›€์„ ์ฃผ๊ณ ์ž ํ•˜๋Š” ๋ชจ๋“  ๋ถ„๋“ค์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ์ดˆ ์น˜ํŠธ์‹œํŠธ (Basic Cheatsheet)

๊ธฐ์ดˆ ์น˜ํŠธ์‹œํŠธ ๋ชฉ์ฐจ

๋ชฉ์ฐจ ํ™•์žฅํ•˜๊ธฐ

์„น์…˜ 1: React์— TypeScript ์„ค์ •ํ•˜๊ธฐ

์‹œ์ž‘ํ•˜๊ธฐ ์ „ ํ•„์š”ํ•œ ์‚ฌํ•ญ

  1. React์— ๋Œ€ํ•œ ์ถฉ๋ถ„ํ•œ ์ดํ•ด
  2. TypeScript Types์ฃผ์ œ์— ๋Œ€ํ•œ ์ดํ•ด (2ality's guide๋ฅผ ์•Œ๊ณ ์žˆ์œผ๋ฉด ๋ฌธ์„œ๋ฅผ ์ดํ•ดํ•˜๋Š”๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ TypeScript๋ฅผ ์ฒ˜์Œ ์ ‘ํ•˜๋Š” ๋ถ„์ด๋ผ๋ฉด,chibicodeโ€™s tutorial๋ฅผ ์ฐธ๊ณ ํ•ด ๋ณด์„ธ์š”.)
  3. the TypeScript section in the official React docs ์ฝ๊ธฐ
  4. the React section of the new TypeScript playground ์ฝ๊ธฐ (์„ ํƒ์‚ฌํ•ญ: the playground's Example Section์˜ 40+ examples ๋‹จ๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•ด ๋ณด๊ธฐ)

์ด ๊ฐ€์ด๋“œ๋Š” ๋…์ž๊ฐ€ ๊ฐ€์žฅ ์ตœ์‹  ๋ฒ„์ „์˜ TypeScript์™€ React๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด์ „ ๋ฒ„์ „์— ๋Œ€ํ•œ ์‚ฌํ•ญ์€ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ <details> ํƒœ๊ทธ๋กœ ํ™•์ธ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

VS Code ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ(Extensions)

React + TypeScript ์ž…๋ฌธ์ž ํ‚คํŠธ

Cloud setups:

  • TypeScript Playground with React๋Š” ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ  Types๋ฅผ ๋””๋ฒ„๊น…๋งŒ ํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • CodeSandbox - cloud IDE, ๋งค์šฐ ๋น ๋ฅธ ๋ถ€ํŒ… ์†๋„๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
  • Stackblitz - cloud IDE, ๋งค์šฐ ๋น ๋ฅธ ๋ถ€ํŒ… ์†๋„๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

Local dev setups:

  • Next.js: npx create-next-app -e with-typescript ๋ช…๋ น์–ด๋Š” ์ƒˆ๋กœ์šด NextJS ํ”„๋กœ์ ํŠธ๋ฅผ ํ˜„์žฌ ํด๋”์— ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • Create React App: npx create-react-app name-of-app --template typescript ๋ช…๋ น์–ด๋Š” ์ƒˆ๋กœ์šด NextJS ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒˆ๋กœ์šด ํด๋”์— ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • Vite: npm create vite@latest my-react-ts-app -- --template react-ts
  • Meteor: meteor create --typescript name-of-my-new-typescript-app
  • Ignite for React Native: ignite new myapp
  • TSDX: npx tsdx create mylib ๋ช…๋ น์–ด๋Š” React+TS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. (in future: TurboRepo)
๋‹ค๋ฅธ ๋„๊ตฌ๋“ค

์•„์ง ๋ณด์™„์ด ํ•„์š”ํ•˜์ง€๋งŒ ํ™•์ธํ•ด ๋ณผ ๋งŒํ•œ ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š” ๋„๊ตฌ๋“ค:

Manual setup:

๋น„๋””์˜ค ํŠœํ† ๋ฆฌ์–ผ

์•„๋ž˜์˜ 7๋ถ€๋กœ ๊ตฌ์„ฑ๋œ "React Typescript Course" ๋น„๋””์˜ค ์‹œ๋ฆฌ์ฆˆ๋ฅผ ํ†ตํ•ด TypeScript with React์— ๋Œ€ํ•œ ์†Œ๊ฐœ๋ฅผ ๋“ค์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

react typescript course video series

Section 2: ์‹œ์ž‘ํ•˜๊ธฐ

ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ

ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋Š” props๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›๊ณ  JSX element๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ํ•จ์ˆ˜๋กœ ์ž‘์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// props์˜ ํƒ€์ž… ์ •์˜ - ๋” ๋งŽ์€ ์˜ˆ์‹œ๋Š” "์ปดํฌ๋„ŒํŠธ Props ํƒ€์ดํ•‘"์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
type AppProps = {
  message: string;
}; /* export ํ•œ๋‹ค๋ฉด consumer๊ฐ€ extendํ•  ์ˆ˜ ์žˆ๋„๋ก `interface`๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. */

// ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•; return type์€ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค.
const App = ({ message }: AppProps) => <div>{message}</div>;

// ์‹ค์ˆ˜๋กœ ๋‹ค๋ฅธ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•˜์˜€์„ ๋•Œ ์—๋Ÿฌ๊ฐ€ raise ๋˜๋„๋ก return type์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
const App = ({ message }: AppProps): JSX.Element => <div>{message}</div>;

// type ์„ ์–ธ์„ ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ ์„ ์–ธ์— ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.;์ด ๋ฐฉ๋ฒ•์€ prop types์— ์ด๋ฆ„์„ ๋ถ™์ด์ง€ ์•Š์•„๋„ ๋˜์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ๋ฐ˜๋ณต๋ฉ๋‹ˆ๋‹ค.
const App = ({ message }: { message: string }) => <div>{message}</div>;

Tip: type destructure ์„ ์–ธ์„ ์œ„ํ•ด Paul Shen's VS Code Extension๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. (keyboard shortcut์„ ์ถ”๊ฐ€ ํ•˜์„ธ์š”.)

React.FC๊ฐ€ ๊ถŒ์žฅ๋˜์ง€ ์•Š๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ์š”? React.FunctionComponent/React.VoidFunctionComponent๋Š” ์–ด๋–ค๊ฐ€์š”?

React+TypeScript codebases์—์„œ ๋‹ค์Œ ๋ณด์•˜์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const App: React.FunctionComponent<{ message: string }> = ({ message }) => <div>{message}</div>;

ํ•˜์ง€๋งŒ, ํ˜„์žฌ React.FunctionComponent (๋˜๋Š” ๊ฐ„๋žตํ•˜๊ฒŒ ์จ์„œ React.FC)๋Š” ๊ถŒ์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์— ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ๋žŒ๋“ค์ด ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์ด ์ฃผ์ œ์— ๋Œ€ํ•œ ๋ฏธ๋ฌ˜ํ•œ ์˜๊ฒฌ ์ฐจ์ด๊ฐ€ ์žˆ์„ ์ˆ˜๋Š” ์žˆ์ง€๋งŒ, ๋งŒ์•ฝ ์ด ์˜๊ฒฌ์— ๋™์˜ํ•˜๊ณ  React.FC๋ฅผ ๋‹น์‹ ์˜ ์ฝ”๋“œ๋ฒ ์ด์Šค์—์„œ ์ œ๊ฑฐํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์ด jscodeshift codemond๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"์ผ๋ฐ˜์ ์ธ ํ•จ์ˆ˜" ๋ฒ„์ „๊ณผ์˜ ์ฐจ์ด์ ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • React.FunctionComponent๋Š” return type์„ ๋ช…์‹œ์ ์œผ๋กœ ๋ฐํž™๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์ธ ํ•จ์ˆ˜ ๋ฒ„์ „์€ ์•”์‹œ์ ์ž…๋‹ˆ๋‹ค(๋˜๋Š” ์ถ”๊ฐ€์ ์ธ ์–ด๏ฟฝ๏ฟฝ๏ฟฝํ…Œ์ด์…˜(annotation)์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค).

  • displayName, propTypes, ๊ทธ๋ฆฌ๊ณ  defaultProps์™€ ๊ฐ™์€ static properties๋ฅผ ์œ„ํ•œ ์ž๋™์™„์„ฑ(autocomplete)๊ณผ ํƒ€์ž… ์ฒดํฌ(Typechecking)๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

    • React.FunctionComponent์™€ ํ•จ๊ป˜ defaultProps์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ๋ช‡ ๊ฐ€์ง€ ์•Œ๋ ค์ง„ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์„ธ์š”. ์šฐ๋ฆฌ๋Š” ๊ฐœ๋ฐœ์ž๋‹˜์ด ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ณ„๊ฐœ์˜ defaultProps ์„น์…˜์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • React 18 type ์—…๋ฐ์ดํŠธ ์ด์ „์—๋Š”, React.FunctionComponent์ด children์— ๋Œ€ํ•œ ์•”์‹œ์ ์ธ ์ •์˜(implicit definition)๋ฅผ ์ œ๊ณตํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์—ด๋ค ํ† ๋ก  ๊ณผ์ •์„ ๊ฑฐ์ณค๊ณ  ๊ฒฐ๊ณผ์ ์œผ๋กœ React.FC๊ฐ€ Create React App TypeScript template์—์„œ ์ œ๊ฑฐ๋œ ์ด์œ  ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

// React 18 types ์ด์ „
const Title: React.FunctionComponent<{ title: string }> = ({ children, title }) => (
  <div title={title}>{children}</div>
);
(Deprecated)React.VoidFunctionComponent ๋˜๋Š” React.VFC ์‚ฌ์šฉํ•˜๊ธฐ

@types/react 16.9.48์—์„œ, React.VoidFunctionComponent ๋˜๋Š” React.VFC type์€ children์„ ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ดํ•‘(typing) ํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, React.VFC์™€ React.VoidFunctionComponent๋Š” React 18 (DefinitelyTyped/DefinitelyTyped#59882) ์—์„œ ๋”์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค(deprecated). ๋”ฐ๋ผ์„œ ์ด ์ž„์‹œ๋ฐฉํŽธ์€ React 18+ ์—์„œ ๋”์ด์ƒ ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์ธ ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋‚˜ React.FC๋ฅผ ์‚ฌ์šฉํ•ด ์ฃผ์„ธ์š”.

type Props = { foo: string };

// ์ง€๊ธˆ์€ ๊ดœ์ฐฎ์ง€๋งŒ, ๋ฏธ๋ž˜์—๋Š” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
const FunctionComponent: React.FunctionComponent<Props> = ({ foo, children }: Props) => {
  return (
    <div>
      {foo} {children}
    </div>
  ); // OK
};

// ์ง€๊ธˆ์€ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ , ๋ฏธ๋ž˜์—๋Š” ๋”์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์„๊ฒƒ์ž…๋‹ˆ๋‹ค.(Deprecated)
const VoidFunctionComponent: React.VoidFunctionComponent<Props> = ({ foo, children }) => {
  return (
    <div>
      {foo}
      {children}
    </div>
  );
};
  • ๋ฏธ๋ž˜์—๋Š”, props๋ฅผ ์ž๋™์œผ๋กœ readonly ๋ผ๊ณ  ํ‘œ์‹œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, props ๊ฐ์ฒด๊ฐ€ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฆฌ์ŠคํŠธ์—์„œ destructure ๋œ๋‹ค๋ฉด, ์ด๊ฒƒ์€ ์˜๋ฏธ์—†๋Š” ํ–‰๋™ ์ž…๋‹ˆ๋‹ค.

๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์—๋Š” ์–ด๋–ค syntax๋ฅผ ์‚ฌ์šฉํ•˜๋˜์ง€ ํฐ ์ฐจ์ด๊ฐ€ ์—†์ง€๋งŒ, React.FunctionComponent์˜ ๋ณด๋‹ค ๋ช…์‹œ์ ์ธ ํŠน์„ฑ์„ ์„ ํ˜ธํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ฃผ์˜ํ•ด์•ผ ํ•  ์‚ฌํ•ญ

๋‹ค์Œ์˜ ํŒจํ„ด์€ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. :

์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง(conditional rendering)

const MyConditionalComponent = ({ shouldRender = false }) => (shouldRender ? <div /> : false); // JS ์—์„œ๋„ ์ด๋ ‡๊ฒŒ ํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค.
const el = <MyConditionalComponent />; // ์—๋Ÿฌ๋ฅผ throw ํ•ฉ๋‹ˆ๋‹ค.

์ด ํŒจํ„ด์ด ์ง€์›๋˜์ง€ ์•Š๋Š” ์ด์œ ๋Š” ์ปดํŒŒ์ผ๋Ÿฌ์˜ ํ•œ๊ณ„ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋Š” JSX expression ๋˜๋Š” null ์ด์™ธ์˜ ๋‹ค๋ฅธ ์–ด๋–ค ๊ฒƒ๋„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์ด ๋ฐ˜ํ™˜๋œ๋‹ค๋ฉด ํ•ด๋‹น ํƒ€์ž…์€ Element์— ํ• ๋‹น๋  ์ˆ˜ ์—†๋‹ค๋Š” ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๊ฒŒ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ("{the other type} is not assignable to Element.")

Array.fill

const MyArrayComponent = () => Array(5).fill(<div />);
const el2 = <MyArrayComponent />; // throws an error

์•„์‰ฝ๊ฒŒ๋„ ํ•จ์ˆ˜์˜ ํƒ€์ž…์„ annotate ํ•˜๋Š” ๊ฒƒ์€ ์•„๋ฌด๋Ÿฐ ๋„์›€์ด ๋˜์ง€ ์•Š์„๊ฒƒ์ž…๋‹ˆ๋‹ค. React๊ฐ€ ์ง€์›ํ•˜๋Š” ๋‹ค๋ฅธ ํŠน๋ณ„ํ•œ ํƒ€์ž…(exotic type)์„ ๋ฐ˜ํ™˜ํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด ํƒ€์ž… ํ‘œ๋ช…(type assertion)์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. :

const MyArrayComponent = () => Array(5).fill(<div />) as any as JSX.Element;

์—ฌ๊ธฐ์„œ @ferdaber ์˜ ์„ค๋ช…์„ ํ™•์ธํ•ด๋ณด์„ธ์š”.

Hooks

Hook์€ @types/react v16.8 ์ด์ƒ๋ถ€ํ„ฐ ์ง€์›๋ฉ๋‹ˆ๋‹ค.

useState

ํƒ€์ž… ์ถ”๋ก (Type inference)์€ ๊ฐ„๋‹จํ•œ ๊ฐ’๋“ค์— ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค:

const [state, setState] = useState(false);
// `state` ๋Š” boolean ์œผ๋กœ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค.
// `setState` ๋Š” boolean ๊ฐ’ ๋งŒ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.

ํƒ€์ž… ์ถ”๋ก ์— ๋ณต์žกํ•œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์ถ”๋ก ๋œ ํƒ€์ž…(Inferred Types) ์‚ฌ์šฉํ•˜๊ธฐ ๋„ ํ™•์ธํ•ด๋ณด์„ธ์š”.

ํ•˜์ง€๋งŒ ๋งŽ์€ hook ๋“ค์€ null ๊ฐ™์€ ๊ฐ’๋ฅผ ๋””ํดํŠธ ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™” ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋–ป๊ฒŒ ํƒ€์ž…์„ ์ง€์ •ํ•˜๋Š”์ง€ ๊ถ๊ธˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž…์„ ์„ ์–ธํ•˜๊ณ , union type์„ ์‚ฌ์šฉํ•˜์„ธ์š”.:

const [user, setUser] = useState<User | null>(null);

// later...
setUser(newUser);

๋งŒ์•ฝ useState์„ค์ • ์งํ›„์— state๊ฐ€ ์ดˆ๊ธฐํ™”๋˜๊ณ  ๊ทธ ์ดํ›„์— ํ•ญ์ƒ ๊ฐ’์„ ๊ฐ€์ง„๋‹ค๋ฉด, ํƒ€์ž… ํ‘œ๋ช…(type assertions)์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

const [user, setUser] = useState<User>({} as User);

// later...
setUser(newUser);

์ด ๋ฐฉ๋ฒ•์€ ์ผ์‹œ์ ์œผ๋กœ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ปดํŒŒ์ผ๋Ÿฌ์—๊ฒŒ {}๊ฐ€ User์˜ type์ด๋ผ๊ณ  "๊ฑฐ์ง“๋ง" ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ํ›„์— user state๋ฅผ ์„ค์ •ํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๊ฐ€ user๋Š” User ํƒ€์ž…์ด๋ผ๋Š” ์‚ฌ์‹ค์— ์˜์กด๊ณ  ์ด๊ฒƒ์€ ๋Ÿฐํƒ€์ž… ์—๋Ÿฌ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

useReducer

Reducer actions๋ฅผ ์œ„ํ•ด Discriminated Unions๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. reducer์˜ return type์„ ์ •์˜ํ•˜๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์„ธ์š”. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ return type์„ ์ถ”๋ก ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

import { useReducer } from "react";

const initialState = { count: 0 };

type ACTIONTYPE = { type: "increment"; payload: number } | { type: "decrement"; payload: string };

function reducer(state: typeof initialState, action: ACTIONTYPE) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - Number(action.payload) };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "decrement", payload: "5" })}>-</button>
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>+</button>
    </>
  );
}

TypeScript Playground์—์„œ ๋ณด๊ธฐ

Redux์—์„œ Reducer์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ

Reducer funciton์„ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด redux๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, return type์„ ์ฒ˜๋ฆฌํ•˜๋Š” Reducer<State, Action> ํ˜•์‹์˜ ํŽธ๋ฆฌํ•œ helper๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„์˜ reducer example์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ”๋€” ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. :

import { Reducer } from 'redux';

export function reducer: Reducer<AppState, Action>() {}

useEffect / useLayoutEffect

userEffect์™€ userLayoutEffect ๋‘˜ ๋‹ค side effect๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๊ณ  ์„ ํƒ์ ์œผ๋กœ cleanup function์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋งŒ์•ฝ ์ด hook๋“ค์ด ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, type์ด ํ•„์š” ์—†๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. useEffect๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ํ•จ์ˆ˜ ๋˜๋Š” undefined ์ด์™ธ์˜ ๋‹ค๋ฅธ ๊ฒƒ์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•˜์„ธ์š”. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด TypeScript์™€ React๋Š” ๋‹น์‹ ์—๊ฒŒ ๋น„๋ช…์„ ์ง€๋ฅผ๊ฒƒ์ž…๋‹ˆ๋‹ค. Arros functions๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ด ๋ฌธ์ œ๋Š” ๋‹ค์†Œ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. :

function DelayedEffect(props: { timerMs: number }) {
  const { timerMs } = props;

  useEffect(
    () =>
      setTimeout(() => {
        /* do stuff */
      }, timerMs),
    [timerMs]
  );
  // ๋‚˜์œ ์˜ˆ์‹œ! setTimeout์€ ์•”๋ฌต์ ์œผ๋กœ ์ˆซ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  // arrow function์˜ body๊ฐ€ ์ค‘๊ด„ํ˜ธ๋กœ ๊ฐ์‹ธ์ง€์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  return null;
}
์œ„ ์˜ˆ์‹œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…
function DelayedEffect(props: { timerMs: number }) {
  const { timerMs } = props;

  useEffect(() => {
    setTimeout(() => {
      /* do stuff */
    }, timerMs);
  }, [timerMs]);
  // ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•; ํ™•์‹คํ•˜๊ฒŒ undefined๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ void keyword๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  return null;
}

useRef

TypeScript์—์„œ useRef๋Š” type argument๊ฐ€ ์ดˆ๊ธฐ ๊ฐ’์„ ์™„์ „ํžˆ ํฌํ•จ(cover)ํ•˜๋Š”์ง€ ์•„๋‹Œ์ง€์— ๋”ฐ๋ผread-only๋˜๋Š” mutable ๋‘˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์ž์˜ use case์— ๋งž๋Š” ๊ฒƒ์„ ์„ ํƒํ•˜์„ธ์š”.

Option 1: DOM element ref

DOM element์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”: element type ๋งŒ์„ argument๋กœ ๋„˜๊ฒจ์ฃผ๊ณ  null์„ ์ดˆ๊ธฐ ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด ๊ฒฝ์šฐ์—, ๋ฐ˜ํ™˜๋˜๋Š” reference๋Š” React์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜๋Š” read-only .current๋ฅผ ๊ฐ€์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค. TypeScript๋Š” ์ด ref๋ฅผ element์˜ ref prop์œผ๋กœ ์ „๋‹ฌ ๋ฐ›๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค. :

function Foo() {
  // - ๊ฐ€๋Šฅํ•œ ์ƒ์„ธํ•˜๊ฒŒ ์ž‘์„ฑํ•˜์„ธ์š”. ์˜ˆ๋ฅผ๋“ค๋ฉด, HTMLDivElement๋Š” HTMLElement๋ณด๋‹ค ๋” ์ข‹๊ณ ,
  // Element๋ณด๋‹ค๋Š” ํ›จ์‹  ๋” ์ข‹์€ ์„ ํƒ์ž…๋‹ˆ๋‹ค.
  // - ๊ธฐ์ˆ ์ ์œผ๋กœ ๋งํ•˜์ž๋ฉด, ์ด๊ฒƒ์€ RefObject<HTMLDivElement>๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  const divRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // ref.current๊ฐ€ null์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์ฃผ์˜ํ•˜์„ธ์š”.
    // ์ด๊ฒƒ์€ ๋‹น์‹ ์ด ์กฐ๊ฑด์— ๋”ฐ๋ผ์„œ ref๋œ(ref-ed) element๋ฅผ renderํ•˜๊ฑฐ๋‚˜
    // ํ• ๋‹นํ•˜๋Š” ๊ฒƒ์„ ์žŠ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์ธกํ•  ์ˆ˜ ์žˆ๋Š” ํ˜„์ƒ์ž…๋‹ˆ๋‹ค.
    if (!divRef.current) throw Error("divRef is not assigned");

    // ์ด์ œ divRef.current๋Š” ํ™•์‹คํ•˜๊ฒŒ HTMLDivElement ์ž…๋‹ˆ๋‹ค.
    doSomethingWith(divRef.current);
  });

  // React๊ฐ€ ๋‹น์‹ ์„ ์œ„ํ•ด ref๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜์žˆ๋„๋ก element์—๊ฒŒ ref๋ฅผ ์ „๋‹ฌํ•ด ์ฃผ์„ธ์š”.
  return <div ref={divRef}>etc</div>;
}

๋งŒ์•ฝ divRef.current๊ฐ€ ์ ˆ๋Œ€๋กœ null์ด ์•„๋‹๊ฒƒ์ด๋ผ๋Š” ๊ฒƒ์„ ํ™•์‹ ํ•œ๋‹ค๋ฉด, non-null assertion operator !์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. :

const divRef = useRef<HTMLDivElement>(null!);
// ๋‚˜์ค‘์—... ์ด๊ฒƒ์ด null ์ธ์ง€ ํ™•์ธํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
doSomethingWith(divRef.current);

๋‹น์‹ ์ด type safety๊ฐ€ ๋ณด์žฅ๋œ๋‹ค๊ณ  ๋ฏธ๋ฆฌ ๊ฐ€์ •ํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค๋Š” ๊ฒƒ์— ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ Œ๋”๋ง ๊ณผ์ •์—์„œ ref๋ฅผ element์— ํ• ๋‹นํ•˜๋Š” ๊ฒƒ์„ ์žŠ๊ฑฐ๋‚˜, ref๋œ(ref-ed) element๊ฐ€ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง ๋œ๋‹ค๋ฉด runtime error๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Tip: ์–ด๋–ค HTMLElement๋ฅผ ์‚ฌ์šฉํ• ์ง€ ์„ ํƒํ•˜๊ธฐ

Ref๋Š” ๋ช…์‹œ์„ฑ(specificity)์„ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, HTMLElement ๋งŒ์„ ๋ช…์‹œํ•˜๋Š” ๊ฒƒ์€ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š๋‹ค๋Š” ๋ง์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋‹น์‹ ์ด ํ•„์š”ํ•œ element type์˜ ์ด๋ฆ„์„ ๋ชจ๋ฅธ๋‹ค๋ฉด, lib.dom.ts์—์„œ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์˜๋„์ ์œผ๋กœ type error๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  language service๊ฐ€ type์˜ ์ด๋ฆ„์„ ์•Œ๋ ค์ฃผ๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

image

Option 2: Mutable value ref

mutable value๋ฅผ ๊ฐ€์ง€๊ธฐ ์œ„ํ•ด์„œ๋Š”: ์›ํ•˜๋Š” type์„ ์‚ฌ์šฉํ•˜๊ณ  ์ดˆ๊ธฐ ๊ฐ’์ด ์™„์ „ํžˆ ํ•ด๋‹น type์— ์†ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

function Foo() {
  // ๊ธฐ์ˆ ์ ์œผ๋กœ ๋งํ•˜์ž๋ฉด, ์ด๊ฒƒ์€ MutableRefObject<number | null>์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  const intervalRef = useRef<number | null>(null);

  // ๋‹น์‹ ์ด ์ง์ ‘ ref๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. (์ด๊ฒƒ์ด MutableRefObject๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ์ด์œ ์ด์ฃ .)
  useEffect(() => {
    intervalRef.current = setInterval(...);
    return () => clearInterval(intervalRef.current);
  }, []);

  // ref๋Š” element์˜ "ref" prop์œผ๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  return <button onClick={/* clearInterval the ref */}>Cancel timer</button>;
}
๋‹ค์Œ์˜ ์ž๋ฃŒ๋„ ํ™•์ธํ•ด ๋ณด์„ธ์š”.

useImperativeHandle

ํ•ด๋‹น Stackoverflow answer์— ๋”ฐ๋ฅด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.:

// Countdown.tsx

// forwardRef๋กœ ์ „๋‹ฌ๋  handle type์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค
export type CountdownHandle = {
  start: () => void;
};

type CountdownProps = {};

const Countdown = forwardRef<CountdownHandle, CountdownProps>((props, ref) => {
  useImperativeHandle(ref, () => ({
    // start() ๋Š” ์—ฌ๊ธฐ์„œ ํƒ€์ž… ์ถ”๋ก (type inference) ๋ฉ๋‹ˆ๋‹ค
    start() {
      alert("Start");
    },
  }));

  return <div>Countdown</div>;
});
// ์ด ์ปดํฌ๋Š”ํŠธ๋Š” Countdown ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค

import Countdown, { CountdownHandle } from "./Countdown.tsx";

function App() {
  const countdownEl = useRef<CountdownHandle>(null);

  useEffect(() => {
    if (countdownEl.current) {
      // start()๋Š” ์—ฌ๊ธฐ์„œ๋„ ํƒ€์ž… ์ถ”๋ก (type inference) ๋ฉ๋‹ˆ๋‹ค.
      countdownEl.current.start();
    }
  }, []);

  return <Countdown ref={countdownEl} />;
}
๋‹ค์Œ์˜ ์ž๋ฃŒ๋„ ํ™•์ธํ•ด๋ณด์„ธ์š”:

Custom Hooks

๋งŒ์•ฝ Custom Hook์—์„œ array๋ฅผ returnํ•œ๋‹ค๋ฉด, array์˜ ๊ฐ ์œ„์น˜์—์„œ ๊ฐ๊ธฐ ๋‹ค๋ฅธ type์„ ๊ฐ€์ง€๊ธฐ๋ฅผ ์›ํ•˜๊ฒ ์ง€๋งŒ TypeScript๋Š” union type์œผ๋กœ ์ถ”๋ก ํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ํƒ€์ž… ์ถ”๋ก ์„ ํ”ผํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ TS 3.4 const assertions์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState } from "react";

export function useLoading() {
  const [isLoading, setState] = useState(false);
  const load = (aPromise: Promise<any>) => {
    setState(true);
    return aPromise.finally(() => setState(false));
  };
  return [isLoading, load] as const; // (boolean | typeof load)[]์ด ์•„๋‹Œ [boolean, typeof load]์œผ๋กœ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค.
}

TypeScript Playground์—์„œ ํ™•์ธํ•ด ๋ณด๊ธฐ

์ด๋Ÿฐ ๋ฐฉ๋ฒ•์œผ๋กœ, destructureํ–ˆ์„ ๋•Œ destructuregํ•œ ์œ„์น˜์— ๋”ฐ๋ผ ์˜ฌ๋ฐ”๋ฅธ type์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ€์•ˆ: tuple return type์„ ํ‘œ๋ช…ํ•˜๊ธฐ(assert)

๋งŒ์•ฝ const assertions์ด ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ต๋‹ค๋ฉด, ํ•จ์ˆ˜ return type์„ ํ‘œ๋ช…(assert)ํ•˜๊ฑฐ๋‚˜ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState } from "react";

export function useLoading() {
  const [isLoading, setState] = useState(false);
  const load = (aPromise: Promise<any>) => {
    setState(true);
    return aPromise.finally(() => setState(false));
  };
  return [isLoading, load] as [boolean, (aPromise: Promise<any>) => Promise<any>];
}

๋งŽ์€ custom hooks์„ ์ž‘์„ฑํ•œ๋‹ค๋ฉด, ์ž๋™์œผ๋กœ tuples์˜ ํƒ€์ž…์„ ๋ช…์‹œํ•ด์ฃผ๋Š” helper๋„ ํฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function tuplify<T extends any[]>(...elements: T) {
  return elements;
}

function useArray() {
  const numberValue = useRef(3).current;
  const functionValue = useRef(() => {}).current;
  return [numberValue, functionValue]; // type is (number | (() => void))[]
}

function useTuple() {
  const numberValue = useRef(3).current;
  const functionValue = useRef(() => {}).current;
  return tuplify(numberValue, functionValue); // type is [number, () => void]
}

ํ•˜์ง€๋งŒ React team์€ ๋‘๊ฐœ ์ด์ƒ์˜ ๊ฐ’์„ returnํ•˜๋Š” custom hook์€ tuple ๋Œ€์‹  ์ ์ ˆํ•œ object๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค๋Š” ๊ฒƒ์— ์ฃผ์˜ํ•˜์„ธ์š”.

๋” ๋งŽ์€ Hooks + TypeScript ์— ๊ด€ํ•œ ์ฝ์„ ๊ฑฐ๋ฆฌ:

๋งŒ์•ฝ React Hooks library๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์‚ฌ์šฉ์ž๋“ค์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก types๋ฅผ export ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์„ธ์š”.

React Hooks + TypeScript Libraries ์˜ˆ์‹œ:

์ถ”๊ฐ€ํ•  ๋‚ด์šฉ์ด ์žˆ๋‚˜์š”? issue๋ฅผ ์žฅ์„ฑํ•˜์„ธ์š”!.

Class Components

TypeScript์—์„œ React.Component๋Š” generic type (aka React.Component<PropType, StateType>)์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ React.Component์— prop๊ณผ state type parameter๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. :

type MyProps = {
  // `interface`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค
  message: string;
};
type MyState = {
  count: number; // ์ด๋Ÿฐ ์‹์œผ๋กœ
};
class App extends React.Component<MyProps, MyState> {
  state: MyState = {
    // ๋” ๋‚˜์€ ํƒ€์ž… ์ถ”๋ก ์„ ์œ„ํ•ด ์„ ํƒ์ ์œผ๋กœ ์ž‘์„ฑํ•œ ๋‘ ๋ฒˆ์งธ annotation
    count: 0,
  };
  render() {
    return (
      <div>
        {this.props.message} {this.state.count}
      </div>
    );
  }
}

TypeScript Playground ํ™•์ธํ•ด ๋ณด๊ธฐ

์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ด๋Ÿฌํ•œ types/interfaces๋ฅผ export/import/extend ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์„ธ์š”.

์™œ state๋ฅผ ๋‘ ๋ฒˆ annotate ํ• ๊นŒ์š”?

๋ฐ˜๋“œ์‹œ state class property์— annotateํ•  ํ•„์š”๋Š” ์—†์ง€๋งŒ, ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด this.state์— ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜ state๋ฅผ ์ดˆ๊ธฐํ™” ํ•  ๋•Œ ๋” ๋‚˜์€ ํƒ€์ž… ์ถ”๋ก ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ ์ด์œ ๋Š” ๋‘ ๊ฐœ์˜ annotation์€ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ generic type parameter๋Š” this.setState()๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์ด ๋ฉ”์†Œ๋“œ๋Š” base class์—์„œ ์˜ค๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ state๋ฅผ ์ดˆ๊ธฐํ™” ํ•˜๋Š” ๊ฒƒ์€ base implementation์„ overrideํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ปดํŒŒ์ผ๋Ÿฌ์—๊ฒŒ ์‚ฌ์‹ค์ƒ ๋‹ค๋ฅธ ์ž‘์—…์„ ํ•˜๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. (=์ปดํŒŒ์ผ๋Ÿฌ์—๊ฒŒ ์‚ฌ์‹ค์ƒ ๊ฐ™์€ ์ž‘์—…์„ ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.)

์—ฌ๊ธฐ์„œ @ferdaber์˜ ์˜๊ฒฌ์„ ํ™•์ธํ•ด๋ณด์„ธ์š”.

readonly๋Š” ํ•„์š” ์—†๋‹ค

์ข…์ข… ์ƒ˜ํ”Œ ์ฝ”๋“œ์— props์™€ state๊ฐ€ ๋ณ€ํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด readonly๋ฅผ ํฌํ•ฉํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

type MyProps = {
  readonly message: string;
};
type MyState = {
  readonly count: number;
};

React.Component<P,S>๊ฐ€ ์ด๋ฏธ props์™€ state๊ฐ€ ๋ณ€ํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ํ‘œ์‹œํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€์ ์œผ๋กœ readonlyํ‘œ์‹œ๋ฅผ ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. (PR ๊ณผ discussion์„ ํ™•์ธํ•˜์„ธ์š”!)

Class Methods: ์›๋ž˜ ํ•˜๋˜๋ฐ๋กœ ํ•˜๋˜, ๋‹น์‹ ์˜ ํ•จ์ˆ˜๋ฅผ ์œ„ํ•œ ๋ชจ๋“  arguments๋Š” type์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ๋งŒ ๊ธฐ์–ตํ•˜์„ธ์š”.

class App extends React.Component<{ message: string }, { count: number }> {
  state = { count: 0 };
  render() {
    return (
      <div onClick={() => this.increment(1)}>
        {this.props.message} {this.state.count}
      </div>
    );
  }
  increment = (amt: number) => {
    // ์ด๋Ÿฐ ์‹์œผ๋กœ
    this.setState((state) => ({
      count: state.count + amt,
    }));
  };
}

TypeScript Playground์—์„œ ํ™•์ธํ•ด ๋ณด๊ธฐ

Class Properties: ๋งŒ์•ฝ ๋‚˜์ค‘์— ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด class properties๋ฅผ ์„ ์–ธํ•œ๋‹ค๋ฉด, state์™€ ๊ฐ™์ด ์„ ์–ธํ•˜๋˜ ํ• ๋‹น์€ ํ•˜์ง€ ์•ˆ์Šต๋‹ˆ๋‹ค.

class App extends React.Component<{
  message: string;
}> {
  pointer: number; // ์ด๋Ÿฐ ์‹์œผ๋กœ
  componentDidMount() {
    this.pointer = 3;
  }
  render() {
    return (
      <div>
        {this.props.message} and {this.pointer}
      </div>
    );
  }
}

TypeScript Playground์—์„œ ํ™•์ธํ•ด ๋ณด๊ธฐ

์ถ”๊ฐ€ํ•  ๋‚ด์šฉ์ด ์žˆ๋‚˜์š”? issue๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”!.

getDerivedStateFromProps ํƒ€์ž…ํ•‘(Typing) ํ•˜๊ธฐ

getDerivedStateFromProps๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์—, documentation๊ณผ You Probably Don't Need Derived State๋ฅผ ์ฝ์–ด๋ณด์„ธ์š”. Derived State๋Š” memoization์„ ์„ค์ •ํ•œ๋Š” ๊ฒƒ์„ ๋„์šธ ์ˆ˜ ์žˆ๋Š” hooks์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ getDerivedStateFromProps๋ฅผ annotateํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

  1. ๋งŒ์•ฝ derived state์˜ type์„ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ–ˆ๊ณ , getDerivedStateFromProps์˜ return ๊ฐ’์ด ์„ค์ •ํ•œ type์„ ์ค€์ˆ˜ํ•˜๋Š”์ง€ ์•Œ๊ณ ์‹ถ์€ ๊ฒฝ์šฐ
class Comp extends React.Component<Props, State> {
  static getDerivedStateFromProps(props: Props, state: State): Partial<State> | null {
    //
  }
}
  1. ํ•จ์ˆ˜์˜ return ๊ฐ’์ด state๋ฅผ ๊ฒฐ์ •ํ•˜๋„๋ก ํ•˜๊ณ ์‹ถ์€ ๊ฒฝ์šฐ
class Comp extends React.Component<Props, ReturnType<typeof Comp["getDerivedStateFromProps"]>> {
  static getDerivedStateFromProps(props: Props) {}
}
  1. ๋‹ค๋ฅธ state fields์™€ derived state ๊ทธ๋ฆฌ๊ณ  memoization์„ ์›ํ•  ๊ฒฝ์šฐ
type CustomValue = any;
interface Props {
  propA: CustomValue;
}
interface DefinedState {
  otherStateField: string;
}
type State = DefinedState & ReturnType<typeof transformPropsToState>;
function transformPropsToState(props: Props) {
  return {
    savedPropA: props.propA, // save for memoization
    derivedState: props.propA,
  };
}
class Comp extends React.PureComponent<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      otherStateField: "123",
      ...transformPropsToState(props),
    };
  }
  static getDerivedStateFromProps(props: Props, state: State) {
    if (isEqual(props.propA, state.savedPropA)) return null;
    return transformPropsToState(props);
  }
}

TypeScript Playground์—์„œ ํ™•์ธํ•ด ๋ณด๊ธฐ

defaultProps๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค

์ด ํŠธ์œ—์— ๋”ฐ๋ฅด๋ฉด, defaultProps๋Š” deprecate ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.. ๋‹ค์Œ์˜ ํ† ๋ก ์„ ํ™•์ธํ•ด ๋ณด์„ธ์š”.:

  • Original tweet
  • ์ด article์—์„œ ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

object default value๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ†ต์ƒ์ ์œผ๋กœ ํ•ฉ์˜๋œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

Function Components:

type GreetProps = { age?: number };

const Greet = ({ age = 21 }: GreetProps) => // etc

Class Components:

type GreetProps = {
  age?: number;
};

class Greet extends React.Component<GreetProps> {
  render() {
    const { age = 21 } = this.props;
    /*...*/
  }
}

let el = <Greet age={3} />;

defaultProps ํƒ€์ดํ•‘ ํ•˜๊ธฐ

TypeScript 3.0+์—์„œ ํƒ€์ž… ์ถ”๋ก ์˜ ์„ฑ๋Šฅ์ด ๋งค์šฐ ๋งŽ์ด ๋ฐœ์ „๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ช‡๋ช‡์˜ edge case ๋“ค์ด ๋ฌธ์ œ๊ฐ€ ๋˜๊ธฐ๋Š” ํ•ฉ๋‹ˆ๋‹ค..

Function Components

// ์‰ฌ์šด ๋ฐฉ๋ฒ•์œผ๋กœ typeof๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.; hoist๋˜๋Š” ๊ฒƒ์— ์ฃผ์˜ํ•˜์„ธ์š”!
// DefaultProps์„ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
// e.g. https://github.com/typescript-cheatsheets/react/issues/415#issuecomment-841223219
type GreetProps = { age: number } & typeof defaultProps;

const defaultProps = {
  age: 21,
};

const Greet = (props: GreetProps) => {
  // etc
};
Greet.defaultProps = defaultProps;

TS Playground์—์„œ ํ™•์ธํ•ด๋ณด๊ธฐ

Class components์˜ ๊ฒฝ์šฐ, ํƒ€์ดํ•‘์„ ์œ„ํ•œ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• (including using the Pick utility type)์ด ์žˆ์ง€๋งŒ, props definition์„ ๊ฑฐ๊พธ๋กœ ์ˆ˜ํ–‰("reverse") ํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.

type GreetProps = typeof Greet.defaultProps & {
  age: number;
};

class Greet extends React.Component<GreetProps> {
  static defaultProps = {
    age: 21,
  };
  /*...*/
}

// Type-checks! type assertion์ด ํ•„์š” ์—†์Œ!
let el = <Greet age={3} />;
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž‘์„ฑ์ž๋ฅผ ์œ„ํ•œJSX.LibraryManagedAttributes ๋‰˜์•™์Šค

์œ„์—์„œ ์†Œ๊ฐœ๋œ ๊ตฌํ˜„์€ ์•ฑ ๊ฐœ๋ฐœ์ž๋“ค์ด ์‚ฌ์šฉํ•˜๋Š”๋ฐ ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์‚ฌ์šฉ(consume)ํ•  ์ˆ˜ ์žˆ๋„๋ก GreetProps๋ฅผ export ํ•˜๊ณ ์‹ถ์€ ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ GreetProps๊ฐ€ ์ •์˜๋˜๋Š” ๋ฐฉ๋ฒ•์ด ๋ฌธ์ œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. age๋Š” ๊ผญ ํ•„์š”ํ•˜์ง€ ์•Š์„ ๋•Œ์—๋„ defaultProps๋•Œ๋ฌธ์— ํ•„์ˆ˜์ ์ธ props๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

GreetProps๋Š” ๋‹น์‹ ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์œ„ํ•œ ๋‚ด๋ถ€์ ์ธ ๊ทœ์น™(์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ)์ด์ง€, ์™ธ๋ถ€์ ์ธ ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ export๋ฅผ ์œ„ํ•œ type์„ ๋”ฐ๋กœ ๋งŒ๋“ค ๊ฑฐ๋‚˜, JSX.LibraryManagedAttributes utility๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

// internal contract(๋‚ด๋ถ€์ ์ธ ๊ทœ์น™), export ๋˜์–ด์„œ๋Š” ์•ˆ๋œ๋‹ค
type GreetProps = {
  age: number;
};

class Greet extends Component<GreetProps> {
  static defaultProps = { age: 21 };
}

// external contract(์™ธ๋ถ€์ ์ธ ๊ทœ์น™)
export type ApparentGreetProps = JSX.LibraryManagedAttributes<typeof Greet, GreetProps>;

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ์ž˜ ์‹คํ–‰๋˜์ง€๋งŒ, ApparentGreetProps๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ๋‹ค์†Œ ๋ฒˆ๊ฑฐ๋กœ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์—์„œ ์„ค๋ช…ํ•˜๋Š” ComponentProps utility๋กœ boilerplate๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

defaultProps๊ฐ€ ์žˆ๋Š” Component์˜ props๋ฅผ ์‚ฌ์šฉ(consume)ํ•˜๊ธฐ

defaultProps๊ฐ€ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ์‹ค์ œ๋กœ๋Š” ๊ทธ๋ ‡์ง€ ์•Š์ง€๋งŒ ํ•„์ˆ˜์ ์ธ props๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Problem Statement

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ํ•˜๊ณ ์‹ถ๋‹ค๋ฉด,

interface IProps {
  name: string;
}
const defaultProps = {
  age: 25,
};
const GreetComponent = ({ name, age }: IProps & typeof defaultProps) => (
  <div>{`Hello, my name is ${name}, ${age}`}</div>
);
GreetComponent.defaultProps = defaultProps;

const TestComponent = (props: React.ComponentProps<typeof GreetComponent>) => {
  return <h1 />;
};

// 'age' property๋Š” '{ name: string; }'์— ์—†์ง€๋งŒ, '{ age: number; } type์—์„œ๋Š” ํ•„์ˆ˜์ ์ธ property ์ž…๋‹ˆ๋‹ค.
const el = <TestComponent name="foo" />;
Solution

JSX.LibraryManagedAttributes๋ฅผ ์ ์šฉํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

type ComponentProps<T> = T extends React.ComponentType<infer P> | React.Component<infer P>
  ? JSX.LibraryManagedAttributes<T, P>
  : never;

const TestComponent = (props: ComponentProps<typeof GreetComponent>) => {
  return <h1 />;
};

// No error
const el = <TestComponent name="foo" />;

TS Playground์—์„œ ํ™•์ธํ•ด ๋ณด๊ธฐ

์—ฌ๋Ÿฌ๊ฐ€์ง€ ํ† ๋ก ๊ณผ ์ง€์‹

React.FC๋Š” ์™œ defaultProps์ด ๋™์ž‘ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋งŒ๋“ค๊นŒ์š”?

๋‹ค์Œ์˜ ํ† ๋ก ์„ ํ™•์ธํ•ด ๋ณด์„ธ์š”.:

์ด๊ฑด ํ˜„์žฌ ์ƒํƒœ์ผ ๋ฟ์ด๊ณ , ์ถ”ํ›„์—๋Š” ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ˆ˜์ •๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

TypeScript 2.9 and earlier

TypeScript 2.9์™€ ๊ทธ ์ด์ „ ๋ฒ„์ „์—์„œ๋Š” ์ด๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‹ค์Œ ๋ฐฉ๋ฒ•์ด ์—ฌํƒœ๊นŒ์ง€ ํ™•์ธํ•œ ๋ฐฉ๋ฒ• ์ค‘ ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

type Props = Required<typeof MyComponent.defaultProps> & {
  /* ์ถ”๊ฐ€์ ์ธ props */
};

export class MyComponent extends React.Component<Props> {
  static defaultProps = {
    foo: "foo",
  };
}

์ด์ „์—๋Š” TypeScript์˜ Partial type ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ ์‚ฌํ•ญ์ด์—ˆ๋Š”๋ฐ, ์ด๋Š” ํ˜„์žฌ ์ธํ„ฐํŽ˜์ด๏ฟฝ๏ฟฝ๊ฐ€ ๋ž˜ํ•‘๋œ ์ธํ„ฐํŽ˜์ด์Šค์—์—์„œ ๋ถ€๋ถ„์ ์ธ ๋ฒ„์ „์„ ์ถฉ์กฑํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋ฐฉ๋ฒ•์œผ๋กœ type์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  defaultProps๋ฅผ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

interface IMyComponentProps {
  firstProp?: string;
  secondProp: IPerson[];
}

export class MyComponent extends React.Component<IMyComponentProps> {
  public static defaultProps: Partial<IMyComponentProps> = {
    firstProp: "default",
  };
}

์ด ์ ‘๊ทผ ๋ฐฉ๋ฒ•์˜ ๋ฌธ์ œ์ ์€ JSX.LibraryManagedAttributes๋กœ ์ž‘๋™ํ•˜๋Š” ํƒ€์ž… ์ถ”๋ก ์— ๋ณต์žกํ•œ ์ด์Šˆ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋กœ JSX expression์„ ์ƒ์„ฑํ•  ๋•Œ ๋ชจ๋“  props๊ฐ€ ์„ ํƒ์‚ฌํ•ญ(optional)์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

@ferdaber์˜ ์˜๊ฒฌ์„ ํ™•์ธํ•ด ๋ณด์„ธ์š”. and here.

์ถ”๊ฐ€ํ•  ๋‚ด์šฉ์ด ์žˆ๋‚˜์š”? ์ด์Šˆ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”!.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published