create-react-app
에 typescript
를 추가하는 방법은 두 가지가 있다.npx create-react-app my-app --template typescript
or
yarn create react-app my-app --template typescript
ts
탬플릿을 달아 재설치한다.npm install --save typescript @types/node @types/react @types/react-dom @types/jest
or
yarn add typescript @types/node @types/react @types/react-dom @types/jest
ts
패키지를 추가한다..js
컴포넌트를 .tsx
컴포넌트로 변경한다.js
로 compile하기 위한 tsconfig.json
을 생성해 컴파일 옵션을 추가한다.ts
를 추가한 후 index.tsx
에서 아래와 같은 에러가 발생했다.> const root = ReactDOM.createRoot(document.getElementById("root"));
TS2345: Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element | DocumentFragment'.
Type 'null' is not assignable to type 'Element | DocumentFragment'.
root
아이디를 가진 엘리먼트 타입을 ts
가 모르기 때문에 발생한 에러이다.document
뒤에 as
를 추가해 역할을 알려준다.const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
ts
에서 사용할 수 있는 Styled Components
로 재설치한다.npm install --save @types/styled-components
Styled Components
에 props
를 전달해 사용하려면 그 타입을 정의해줘야 한다.import styled from "styled-components";
interface ContainerProps {
bgColor: string;
}
const Container = styled.div<ContainerProps>`
width: 200px;
height: 200px;
background-color: ${(props) => props.bgColor};
`;
function TsContainer({ bgColor }: ContainerProps) {
return <Container bgColor={bgColor} />;
}
export default TsContainer;
props?:datatype
으로 선언하면 선택적으로 사용가능한 프로퍼티가 된다.{props ?? default}
를 사용하면, 해당 프로퍼티에 값이 없을 경우 default
를 기본 프로퍼티값으로 갖는다.optional(props?)
로 선언하고, 컴포넌트 프로퍼티에 값을 할당해도 default
값을 갖는다.import styled from "styled-components";
interface ContainerProps {
bgColor: string;
borderColor?: string;
text?: string;
}
const Container = styled.div<ContainerProps>`
width: 200px;
height: 200px;
background-color: ${(props) => props.bgColor};
border: 2px solid ${(props) => props.borderColor};
text-align: center;
`;
function TsContainer({
bgColor,
borderColor,
text = "Hello Ts",
}: ContainerProps) {
return (
<Container bgColor={bgColor} borderColor={borderColor ?? "black"}>
{text}
</Container>
);
}
export default TsContainer;
ts
환경에서 커스텀 theme
을 사용하려면 해당 theme
의 프로퍼티에 대한 declare
가 필요하다.src
폴더에 styled.d.ts
를 생성한다.declare module
내부에 interface
를 정의한다.// import original module declarations
import "styled-components";
// and extend them!
declare module "styled-components" {
export interface DefaultTheme {
textColor: string;
bgColor: string;
btnColor: string;
}
}
theme
을 생성한다.import { DefaultTheme } from "styled-components";
export const lightTheme: DefaultTheme = {
textColor: "black",
bgColor: "white",
btnColor: "tomato",
};
export const darkTheme: DefaultTheme = {
textColor: "white",
bgColor: "black",
btnColor: "teal",
};
styled-components
와 똑같이 사용하면 된다.// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { ThemeProvider } from "styled-components";
import App from "./App";
import { darkTheme, lightTheme } from "./theme";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<ThemeProvider theme={darkTheme /*or lightTheme*/}>
<App />
</ThemeProvider>
</React.StrictMode>
);
// App.js
import styled from "styled-components";
const Wrapper = styled.div`
background-color: ${(props) => props.theme.bgColor};
`;
const H1 = styled.h1`
color: ${(props) => props.theme.textColor};
`;
const Btn = styled.button`
background-color: ${(props) => props.theme.btnColor};
`;
function App() {
return (
<Wrapper>
<H1>Protected</H1>
<Btn>Hello</Btn>
</Wrapper>
);
}
export default App;
ts
는 useState()
에 넣은 기본값으로 데이터타입을 추론한다.import { useState } from "react";
function Circle() {
const [counter, setCounter] = useState(1);
setCounter("hi"); // error
return <div />;
}
export default Circle;
state
의 기본값이 1
이므로 counter
가 계속 number
를 가질 것으로 추론해 "hi"
를 대입했을 경우 에러를 일으킨다.generics
를 사용해 state
가 가질 수 있는 값을 명시한다.generics
은 단일 타입이 아닌 다양한 타입에서 작동하도록 하는, 개발자가 요구한 대로 signature를 생성해주는 도구이다.import { useState } from "react";
function Circle() {
const [counter, setCounter] = useState<number | string>(1);
setCounter("hi"); // no error
setCounter(true); // error
return <div />;
}
export default Circle;
React
의 form
과 input
은 onSubmit
과 onChange
로 제어된다.ts
환경에서 해당 이벤트를 호출하면 any
타입이라고 경고하며 에러를 발생시킨다.import React, { useState } from "react";
function App() {
const [value, setValue] = useState("");
const onChange = (e: React.FormEvent<HTMLInputElement>) => {
const {
currentTarget: { value },
} = e;
setValue(value);
};
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(`Hi! ${value}`);
};
return (
<div>
<form onSubmit={onSubmit}>
<input type={"text"} placeholder="username" onChange={onChange} />
<button>Log in</button>
</form>
</div>
);
}
export default App;
React
의 이벤트는 브라우저 고유 이벤트가 아닌 SyntheticEvent(합성 이벤트)
이다.ES6
문법 중 하나인 구조 분해 할당은 객체가 가진 프로퍼티를 개별적으로 꺼내와 초기화하는 형태이다.const 구조분해 = {
info: {
id: "blabla",
tag: "div",
},
actions: {
go: "1",
stop: "2",
},
};
const {
info: { id, tag },
actions: { go, stop },
} = 구조분해;
console.log(id, tag, go, stop);
// blabla div 1 2
const {
info: { id: 아이디, tag: 태그 },
actions: { go: 앞으로, stop: 가만히 },
} = 구조분해;
console.log(아이디, 태그, 앞으로, 가만히);
// blabla div 1 2
참고
노마드 코더 - React JS 마스터클래스
Typescript Docs
React Docs - SyntheticEvent
styled-components - Create a declarations file