TypeScript์ ์ ๋ฌธํ๋ React ๊ฐ๋ฐ์๋ฅผ ์ํ ์นํธ์ํธ(Cheetsheets)

์น ๋คํ๋จผํธ | ์์ดํ | ํ๋ก์ ํธ์ ๊ธฐ์ฌํ๊ธฐ | ์ง๋ฌธํ๊ธฐ
๐ ๋ณธ ๋ฆฌํฌ์งํ ๋ฆฌ๋ @ryan_kim_kr์ ์ํด ๊ด๋ฆฌ๋๊ณ ์์ต๋๋ค. ๊ฐ๋ฐ์๋์ด React์ ํจ๊ป TypeScript๋ฅผ ์ฌ์ฉํด๋ณด๊ณ ์ ํ์๋ค๋ ์ ๋ง ๊ธฐ์ ์์์ด๊ตฐ์! ์๋ชป๋ ๋ถ๋ถ์ด ๋ฐ๊ฒฌ๋์ด ์์ ์ด ํ์ํ๊ฑฐ๋ ๋๋ฝ๋ ๋ถ๋ถ์ด ์๋ค๋ฉด ๊ฐ์ ๋์ด์ผ ํ ์ฌํญ์ ์ด์ ๋ฑ๋กํด ์ฃผ์๊ธฐ ๋ฐ๋๋๋ค. ๐
- ๊ธฐ์ด ์นํธ์ํธ(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์ ๋ํ ์ดํด๊ฐ ์ ํ๋์ด์ผ ํฉ๋๋ค.
โ ๏ธ ์ด ์นํธ์ํธ๋ ์๋กญ๊ฒ ๋ง๋ค์ด์ง ์นํธ์ํธ ์ ๋๋ค. ๋ฐ๋ผ์ ๋์์ ์ฃผ๊ณ ์ ํ๋ ๋ชจ๋ ๋ถ๋ค์ ํ์ํฉ๋๋ค.
๋ชฉ์ฐจ ํ์ฅํ๊ธฐ
- React์ ๋ํ ์ถฉ๋ถํ ์ดํด
- TypeScript Types์ฃผ์ ์ ๋ํ ์ดํด (2ality's guide๋ฅผ ์๊ณ ์์ผ๋ฉด ๋ฌธ์๋ฅผ ์ดํดํ๋๋ฐ ๋์์ด ๋ฉ๋๋ค. ๋ง์ฝ TypeScript๋ฅผ ์ฒ์ ์ ํ๋ ๋ถ์ด๋ผ๋ฉด,chibicodeโs tutorial๋ฅผ ์ฐธ๊ณ ํด ๋ณด์ธ์.)
- the TypeScript section in the official React docs ์ฝ๊ธฐ
- the React section of the new TypeScript playground ์ฝ๊ธฐ (์ ํ์ฌํญ: the playground's Example Section์ 40+ examples ๋จ๊ณ๋ฅผ ์ํํด ๋ณด๊ธฐ)
์ด ๊ฐ์ด๋๋ ๋
์๊ฐ ๊ฐ์ฅ ์ต์ ๋ฒ์ ์ TypeScript์ React๋ฅผ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. ์ด์ ๋ฒ์ ์ ๋ํ ์ฌํญ์ ํ์ฅ ๊ฐ๋ฅํ <details>
ํ๊ทธ๋ก ํ์ธ ๊ฐ๋ฅํฉ๋๋ค.
- ๋ฆฌํํ ๋ง ๋ณด์กฐ https://marketplace.visualstudio.com/items?itemName=paulshen.paul-typescript-toolkit
- R+TS Code Snippets (์ฌ๋ฌ๊ฐ์ง ํ์ฅ ํ๋ก๊ทธ๋จ์ด ์์ต๋๋ค...)
- TypeScript ๊ณต์ ํ์ฅํ๋ก๊ทธ๋จ https://code.visualstudio.com/docs/languages/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)
๋ค๋ฅธ ๋๊ตฌ๋ค
์์ง ๋ณด์์ด ํ์ํ์ง๋ง ํ์ธํด ๋ณผ ๋งํ ๊ฐ์น๊ฐ ์๋ ๋๊ตฌ๋ค:
- Snowpack:
npx create-snowpack-app my-app --template app-template-react-typescript
- Docusaurus v2 with TypeScript Support
- Parcel
- JP Morgan's
modular
: CRA + TS + Yarn Workspaces toolkit.yarn create modular-react-app <project-name>
Manual setup:
- Basarat's guide๋ React + TypeScript + Webpack + Babel ์ ์๋์ผ๋ก ์ค์ ํ ๊ฒฝ์ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ํนํ,
@types/react
์@types/react-dom
๊ฐ ์ค์น๋์ด ์๋์ง ํ์ธ์ด ํ์ํฉ๋๋ค. (์ต์ํ์ง ์์ ๋ด์ฉ์ด๋ผ๋ฉด DefinitelyTyped project ์ ๋ํด ๋ ์์๋ณด์ธ์.) - ๋ํ ๋ง์ React + TypeScript bolierplates๋ค์ด ์์ต๋๋ค. ์ฐ๋ฆฌ์ ๋ค๋ฅธ ๋ฆฌ์์ค ๋ฆฌ์คํธ๋ฅผ ํ์ธํด์ฃผ์ธ์.
์๋์ 7๋ถ๋ก ๊ตฌ์ฑ๋ "React Typescript Course" ๋น๋์ค ์๋ฆฌ์ฆ๋ฅผ ํตํด TypeScript with React์ ๋ํ ์๊ฐ๋ฅผ ๋ค์ ์ ์์ต๋๋ค.
ํจ์ ์ปดํฌ๋ํธ๋ 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;
Hook์ @types/react
v16.8 ์ด์๋ถํฐ ์ง์๋ฉ๋๋ค.
ํ์ ์ถ๋ก (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
ํ์
์ด๋ผ๋ ์ฌ์ค์ ์์กด๊ณ ์ด๊ฒ์ ๋ฐํ์
์๋ฌ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
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>() {}
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;
}
TypeScript์์ useRef
๋ type argument๊ฐ ์ด๊ธฐ ๊ฐ์ ์์ ํ ํฌํจ(cover)ํ๋์ง ์๋์ง์ ๋ฐ๋ผread-only๋๋ mutable ๋ ์ค ํ๋๋ฅผ ๋ฐํํฉ๋๋ค. ๊ฐ์์ use case์ ๋ง๋ ๊ฒ์ ์ ํํ์ธ์.
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์ ์ด๋ฆ์ ์๋ ค์ฃผ๋๋ก ํ ์ ์์ต๋๋ค.
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>;
}
ํด๋น 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 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๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํ๋ค๋ ๊ฒ์ ์ฃผ์ํ์ธ์.
- https://medium.com/@jrwebdev/react-hooks-in-typescript-88fce7001d0d
- https://fettblog.eu/typescript-react/hooks/#useref
๋ง์ฝ React Hooks library๋ฅผ ์์ฑํ๊ณ ์๋ค๋ฉด, ์ฌ์ฉ์๋ค์ด ์ฌ์ฉํ ์ ์๋๋ก types๋ฅผ export ํด์ผ ํ๋ค๋ ๊ฒ์ ์์ง ๋ง์ธ์.
- https://github.com/mweststrate/use-st8
- https://github.com/palmerhq/the-platform
- https://github.com/sw-yx/hooks
์ถ๊ฐํ ๋ด์ฉ์ด ์๋์? issue๋ฅผ ์ฅ์ฑํ์ธ์!.
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ํ๊ธฐ ๋๋ฌธ์ ์ปดํ์ผ๋ฌ์๊ฒ ์ฌ์ค์ ๋ค๋ฅธ ์์
์ ํ๊ณ ์์ง ์๋ค๋ ๊ฒ์ ์๋ ค์ค์ผ ํฉ๋๋ค. (=์ปดํ์ผ๋ฌ์๊ฒ ์ฌ์ค์ ๊ฐ์ ์์
์ ํ๊ณ ์๋ค๋ ๊ฒ์ ์๋ ค์ค์ผ ํฉ๋๋ค.)
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
๋ฅผ ์ฌ์ฉํ๊ธฐ ์ ์, documentation๊ณผ You Probably Don't Need Derived State๋ฅผ ์ฝ์ด๋ณด์ธ์. Derived State๋ memoization์ ์ค์ ํ๋ ๊ฒ์ ๋์ธ ์ ์๋ hooks์ ์ฌ์ฉํ์ฌ ๊ตฌํ๋ ์ ์์ต๋๋ค.
๋ค์์ getDerivedStateFromProps
๋ฅผ annotateํ ์ ์๋ ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์
๋๋ค.
- ๋ง์ฝ derived state์ type์ ๋ช
์์ ์ผ๋ก ์ค์ ํ๊ณ ,
getDerivedStateFromProps
์ return ๊ฐ์ด ์ค์ ํ type์ ์ค์ํ๋์ง ์๊ณ ์ถ์ ๊ฒฝ์ฐ
class Comp extends React.Component<Props, State> {
static getDerivedStateFromProps(props: Props, state: State): Partial<State> | null {
//
}
}
- ํจ์์ return ๊ฐ์ด state๋ฅผ ๊ฒฐ์ ํ๋๋ก ํ๊ณ ์ถ์ ๊ฒฝ์ฐ
class Comp extends React.Component<Props, ReturnType<typeof Comp["getDerivedStateFromProps"]>> {
static getDerivedStateFromProps(props: Props) {}
}
- ๋ค๋ฅธ 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๋ 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} />;
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
๊ฐ ์๋ ์ปดํฌ๋ํธ๋ ์ค์ ๋ก๋ ๊ทธ๋ ์ง ์์ง๋ง ํ์์ ์ธ props๋ฅผ ๊ฐ์ง๋ ๊ฒ์ฒ๋ผ ๋ณด์ผ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ ์์ ์ ํ๊ณ ์ถ๋ค๋ฉด,
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" />;
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
์ด ๋์ํ์ง ๋ชปํ๊ฒ ๋ง๋ค๊น์?
๋ค์์ ํ ๋ก ์ ํ์ธํด ๋ณด์ธ์.:
- https://medium.com/@martin_hotell/10-typescript-pro-tips-patterns-with-or-without-react-5799488d6680
- DefinitelyTyped/DefinitelyTyped#30695
- typescript-cheatsheets/react#87
์ด๊ฑด ํ์ฌ ์ํ์ผ ๋ฟ์ด๊ณ , ์ถํ์๋ ์ฌ๋ฐ๋ฅด๊ฒ ์์ ๋ ๊ฒ์ ๋๋ค.
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)์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.