
앞서 타입스크립트에 대한 기본 내용을 알아보았다.
타입스크립트의 개념 및 기본 타입은 여기에 정리해뒀다.
추가할 부분은 많지만 이론으로만 배우기보단 바로 리액트 프로젝트를 만들어서 적용해 나가는게 좋다고 생각해서 바로 진행해보았다.
create-react-app 의 스크립트 기능을 사용하면 TypeScript 가 적용된 프로젝트를 쉽게 만들 수 있다.
npx create-react-app [프로젝트 이름] --template typescript
npm init react-app [프로젝트 이름] --template typescript

js 파일 -> ts 파일
jsx 파일 -> tsx 파일
tsconfig.json
타입스크립트 설정 파일은 타입스크립트를 자바스크립트로 변환할 때의 설정을 정의해놓는 파일이다.
수많은 속성이 있지만 일단 자주쓰이는 속성이 뭔지 알아보자
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
target : 컴파일된 코드가 어떤 환경에서 실행될 지 정의합니다.
예를 들어, 화살표 함수를 사용하고 target을 es5로 지정했다면 이를 일반 function키워드를 활용한 함수로 컴파일 해줍니다. 그러나, 이를 es6로 설정했다면 화살표 함수 그대로 유지합니다.
module : 컴파일된 코드가 어떤 모듈시스템을 사용할지 정의합니다.
이 값을 만약 common으로 하면 export default Sample 코드는 exports.default = Sample로 변환되지만 값을 es2015로 하면 export default Sample을 그대로 유지합니다.
strict : 모드 타입 체킹 옵션을 활성화합니다.
esModuleInterop : commonjs 모듈 형태로 이뤄진 파일을 es2015 모듈 형태로 불러올 수 있게 해줍니다.
outDir : 컴파일된 파일이 어디에 저장될지 경로를 정합니다.
include : 어떤 파일을 컴파일할 것인지 정합니다.
exclude : 어떤 파일을 컴파일에서 제외할지 정합니다.
더 다양한 옵션들이 있지만 그건 추후에 따로 정리하도록하자!
App.tsx를 보며 이해해보자
import React from 'react';
import Hello from './components/Hello';
const App: React.FC = () => {
const onClick = (name: string) => {
console.log(`${name} says hello`);
};
return (
<>
<Hello name="First"></Hello>
</>
);
};
export default App;
const App: React.FC = () => { ... } 와 같이 화살표함수를 사용하여 컴포넌트가 선언되었다.const App: React.FC를 살펴 보자사용방법
FC 타입은 주로 다음과 같은 형태로 사용한다.
interface AppProps {
name: string;
}
// 인자 props의 타입인 AppProps를 props 옆에 붙이지 않고 React.FC 옆에 붙인다
const App: React.FC<AppProps> = (props) => {
return <div>hello {props.name}</div>
}
하지만 React.FC 는 단점이 있어서 사용을 지양한다.
이 부분은 추후에 정리해야겠다.
그렇다면 리액트에서 함수형 컴포넌트는 어떤 타입으로 선언해야하는가?
가장 일반적이고 간단한 방법은 props 옆에 타입을 정의해 주는 것이다.
interface AppProps {
name: string;
}
const App = (props: AppProps) => {};
Hello.tsx
import React from 'react';
interface HelloProps {
name: string;
onClick(name: string): void;
}
const Hello = ({ name, onClick }: HelloProps) => {
const handleClick = () => onClick(name);
return (
<>
<h1>Hello, {name}</h1>
<div>
<button onClick={handleClick}>Click Me</button>
</div>
</>
);
};
export default Hello;
타입스크립트를 사용하는 리액트 컴포넌트에서 useState 를 사용하여 컴포넌트의 상태를 관리하는 방법을 보자.
예제 1)
import React, { useState } from 'react';
function Counter() {
//제네릭타입으로 state 타입 결정
const [count, setCount] = useState<number>(0);
const onIncrease = () => setCount(count + 1);
const onDecrease = () => setCount(count - 1);
return (
<div>
<h1>{count}</h1>
<div>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
</div>
);
}
export default Counter;
제네릭타입<>으로 state 타입 결정하기!
useState를 사용 할 때 Generics 를 사용하지 않아도 알아서 타입을 유추하기 때문에 생략해도 상관없다
그럼 언제 써야 좋을까?
상태가 null일 수도 있고 아닐수도 있을때 Generics 를 활용하시면 좋다
type Information = { name: string; description: string };
const [info, setInformation] = useState<Information | null>(null);
Main.tsx
const [modalOpen, setModal] = useState(false);
...
return(
<DetailModal modalOpen={modalOpen} setModal={setModal} />
)
위와 같이 상태와 상태관리 함수를 하위 컴포넌트에서 어떻게 받을까?
DetailModal.tsx
interface IModalDataProps {
modalOpen: boolean;
setModal: React.Dispatch<React.SetStateAction<boolean>>;
}
const DetailModal = ({ id, modalOpen, setModal, modalData }: IModalDataProps) => {...}
위와 같이 받으면 된다.
setState함수는 위와 같이 받을 수 있지만 아래(1)처럼 void로 넘길 수도 있다.
하지만 SetStateAction 과 Dispatch를 사용하여(2) 타입을 명확히 해주어 타입스크립트를 사용하는 이점을 챙기자!
setModal: () => void // 1
setModal: React.Dispatch<React.SetStateAction<boolean>>; //2
예제 2) 인풋 상태 관리하기
App.tsx
import React from 'react';
import Hello from './components/Hello';
import Counter from './components/Counter';
import MyForm from './components/MyForm';
const App: React.FC = () => {
const onClick = (name: string) => {
console.log(`${name} says hello`);
};
const onSubmit = (form: { name: string; description: string }) => {
console.log(form);
};
return (
<>
<Hello name="First" onClick={onClick}></Hello>
<hr />
<h1>카운터 예제</h1>
<Counter />
<hr />
<h1>Form 예제</h1>
<MyForm onSubmit={onSubmit}></MyForm>
</>
);
};
export default App;
MyForm.tsx
import React, { useState } from 'react';
type MyFormProps = {
onSubmit(form: { name: string; description: string }): void;
};
const MyForm = ({ onSubmit }: MyFormProps) => {
const [form, setForm] = useState({
name: '',
description: '',
});
const { name, description } = form;
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setForm({
...form,
[name]: value,
});
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit(form);
setForm({
name: '',
description: '',
}); // 초기화
};
return (
<form onSubmit={handleSubmit}>
<input name="name" value={name} onChange={onChange} />
<input name="description" value={description} onChange={onChange} />
<button type="submit">등록</button>
</form>
);
};
export default MyForm;
여기서 살짝 문제가 있었다.
우리는 e의 타입을 모른다.
VSCode 에서 확인하기
우리는 구글에 onChange 의 event type 이 무엇인지 검색하지 않아도 된다.
input 태그의 해당 eventHandler 에 마우스를 올려보면 친절히 알려주기 때문이다.

.onChange?: 뒤의 React.ChangeEventHandler<HTMLInputElement> 가 우리가 찾던 타입이다.
하지만...?
event 는 target을 가지고 있지않다?
에러 발생 : ChangeEventHandler

해결 : ChangeEventHandler => ChangeEvent
지금까지 JavaScript 를 통해 진행했을 때 항상 event 는 target 을 프로퍼티로 가지고 있었다.
그러나 Reaact.ChangeEventHandler 를 타입으로 지정할 경우 event.target 부분에서 에러를 띄어주었다.
이유는 타입의 뒤에 Handler가 붙었기 때문이었다..
React.ChangeEvent<HTMLInputElement> 로 타입을 지정해주니 성공적으로 컴파일 된다.
