TypeScript + React 입문
JavaScript 는 weakly typed 언어이기에, 숫자가 문자열로 될 수도 있고 그랬다가 또 숫자가 될 수도 있다가 null 인지 아닌지 확인하지 못합니다. 추가적으로 자동완성도 우리가 Java / C# / C++ / Python 등의 언어를 사용 할 때처럼 제대로 되지 않습니다. (VSCode 를 사용하면 어느정도 되고 있다고 착각 할 수도 있는데 사실 TypeScript 관련 기능이 이미 기본적으로 돌아가고있어서 되는 거랍니다.) 만약에 TypeScript 를 사용하면, 이러한 불편함을 해결해주어 개발을 훨씬 편하게 해줍니다.
이 튜토리얼에서는, TypeScript 를 맛보기식으로 중요한것들만 조금씩 알아보고, 리액트에서 사용하는 방법을 알아보겠습니다.
TypeScript 를 사용하는 이유
TypeScript 를 프로젝트에서 사용하는 대표적인 이유는 다음과 같습니다.
1. IDE 를 더욱 더 적극적으로 활용 (자동완성, 타입확인)
TypeScript 를 사용하면 자동완성이 굉장히 잘됩니다. 함수를 사용 할 때 해당 함수가 어떤 파라미터를 필요로 하는지, 그리고 어떤 값을 반환하는지 코드를 따로 열어보지 않아도 알 수 있습니다. 추가적으로, 리액트 컴포넌트의 경우 해당 컴포넌트를 사용하게 될 때 props 에는 무엇을 전달해줘야하는지, JSX 를 작성하는 과정에서 바로 알 수 있으며, 컴포넌트 내부에서도 자신의 props 에 어떤 값이 있으며, state 에 어떤 값이 있는지 알 수 있습니다. 또한, 리덕스와 함께 사용하게 되면 connect 로 통하여 props 로 전달해 줄 때에도 자동완성이 되어 굉장히 편리합니다.
2. 실수 방지
함수, 컴포넌트 등의 타입 추론이 되다보니, 만약에 우리가 사소한 오타를 만들면 코드를 실행하지 않더라도 IDE 상에서 바로 알 수 있게 됩니다. 그리고, 예를 들어 null 이나 undefined 일 수도 있는 값의 내부 값 혹은 함수를 호출한다면 (예: 배열의 내장함수) 사전에 null 체킹을 하지 않으면 오류를 띄우므로 null 체킹도 확실하게 할 수 있게 됩니다.
프로젝트 만들기
$ yarn create react-app typescript-sample --typescript
타입 연습
타입스크립트 사용법은 공식 문서의 번역본 을 참고해보면 도움이 될 것입니다. 하지만, 굳이 기초부터 하나하나 살펴보지 않아도 직접 사용해보면서 기본적인 사용법은 금방 터득할 수 있습니다.
let 과 const 사용
index.tsx 의 하단부에 다음 코드를 넣어보세요.
let text: string = '';
let number: number = 5;
const array: number[] = [1, 2, 3];
이런식으로, let 이나 const 를 사용 할 때 :
문자열을 사용하여 해당 값의 타입을 지정해줍니다. 이렇게 한번 지정해주고 나면, 다음부터 이 값을 사용 할 때 이 값이 어떤 타입인지 알 수 있고 만약에 다른 타입을 가진 값을 넣으려고 하면 사전에 에디터 단에서 알 수 있습니다.
그 아래에 이런 코드를 입력해보세요:
number = 'asdf';
text = 5;
array.push('asdf');
그러면 이렇게 오류가 발생하게 됩니다! 오류를 확인하셨다면 방금 작성한 코드들은 지워주세요.
함수 사용
이번엔 함수를 만들어볼까요? 함수의 파라미터의 값에도 타입을 지정해줄 수 있습니다. 예를들어서, 파라미터가 숫자 배열이라고 명시를 해주면, 해당 함수에서 그 배열을 사용하게 될 때 배열의 내장함수들이 자동완성됩니다.
그리고 내장함수를 사용하게 될 때 그 안에 있는 값이 어떤 타입인지도, 알 수 있습니다. 원하는 값에 마우스를 올려보면 알 수 있습니다.
Interface 사용
Interface 는 클래스 혹은 객체를 위한 타입을 만들 때 사용되는 문법입니다.
interface Shape {
getArea(): number;
}
class Circle implements Shape {
r: number;
constructor(r: number) {
this.r = r;
}
getArea() {
return this.r * this.r * 3.14;
}
}
const circle = new Circle(3);
특정 클래스에서 특정 Interface 를 사용하게 될 땐 implements 인터페이스명
을 클래스 선언할 때 뒤에 붙여주면 됩니다.
만약에 getArea를 까먹고 구현하지 않는다면 다음과 같이 오류가 뜹니다:
만약에 일반 객체를 타입 지원하고 싶다면 이렇게 하면 됩니다.
interface Person {
name: string;
}
const person: Person = {
name: '홍길동'
};
interface Programmer extends Person {
skills: string[];
}
const programmer: Programmer = {
name: '홍길동',
skills: ['golang', 'react']
};
function printSkills(p: Programmer){
console.log(p.skills);
}
인터페이스는 다른 인터페이스를 extends
키워드를 사용하여 상속 가능합니다.
이렇게 하면 사용 단계에서 자동완성도 잘 되고:
잘못된 타입을 가진 값을 전달하게 된다면 에러가 발생합니다:
Type 사용
Type 은 Interface 랑 비슷한데 주로 클래스가 아닌 일반 객체를 위한 타입을 지정할 때 사용 됩니다:
type Person = {
name: string;
}
const person: Person = {
name: '홍길동'
};
type Programmer = Person & {
skills: string[];
}
const programmer: Programmer = {
name: '홍길동',
skills: ['golang', 'react']
};
Type 의 경우엔 &
연산자를 사용하여 다른 타입과 결합시킬 수 있습니다.
Interface 와 Type 의 차이점은 여기서 더 자세히 확인 할 수 있습니다.
우리가 나중에 컴포넌트의 props 나 state 를 타입 지원 하게 될 땐 interface 나 type 중 아무거나 사용 할 수 있습니다. 프로젝트에서 일관성있게만 구현하시면 됩니다.
리액트 컴포넌트 만들기
VSCode 에서 TypeScript React Code Snippets를 사용하면 tsrcc, tsrcfull, tsrsfc 등의 단축단어로 컴포넌트를 쉽게 생성 할 수 있습니다. 설치하시는것을 권장드립니다.
함수형 컴포넌트 만들기
한번 카운터를 구현해보겠습니다!
src/Counter.tsx
import * as React from 'react';
interface CounterProps {
onIncrement(): void;
onDecrement(): void;
number: number;
}
const Counter: React.SFC<CounterProps> = props => {
return (
<div>
<h1>{props.number}</h1>
<button onClick={props.onIncrement}>+1</button>
<button onClick={props.onDecrement}>-1</button>
</div>
);
};
export default Counter;
주의하실점은, 리액트 컴포넌트를 만들 땐 언제나 .tsx 확장자를 사용해야 한다는 점 입니다!
클래스형 컴포넌트 만들기
이제 기존에 있던 src/App.js 를 src/App.tsx 로 이름을 바꾸고, 내부에서 카운터를 사용하는 클래스 컴포넌트를 구현해보겠습니다.
import * as React from 'react';
import Counter from './Counter';
export interface AppProps {}
export interface AppState {
number: number;
}
export default class App extends React.Component<AppProps, AppState> {
state = {
number: 1
};
handleIncrement = () => {
this.setState({
number: this.state.number + 1
});
};
handleDecrement = () => {
this.setState({
number: this.state.number + 1
});
};
public render() {
return (
<Counter
number={this.state.number}
onIncrement={this.handleIncrement}
onDecrement={this.handleDecrement}
/>
);
}
}
이 과정에서 TypeScript 사용을 통해 얻을 수 있는 이점을 한번 알아볼까요?
컴포넌트를 사용하는 단계에서 어떤 props 를 필요한지 파일을 열지 않고 알 수 있습니다.
그리고, 필요한 props 를 빼먹으면 미리 에러를 확인 할 수 있습니다.
실수로, state 에 잘못된 타입을 지정하면 에러를 확인 할 수 있습니다.
이제, 여러분들도 TypeScript 를 쓸 줄 아는 리액트 개발자가 되었습니다!