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>;
}