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 체킹도 확실하게 할 수 있게 됩니다.
프로젝트 만들기
create-react-app 의 스크립트 기능을 사용하면 TypeScript 가 적용된 프로젝트를 매우 쉽게 만들 수 있습니다.
$ npx create-react-app typescript-react-app --scripts-version=react-scripts-ts
TypeScript 가 적용된 프로젝트를 생성하였습니다. VSCode 마켓플레이스에서 TSLint 확장 프로그램을 설치하시면 코드 스타일과 기본적인 자바스크립트 문법도 에디터상에서 바로 점검 할 수 있습니다. 조금 더 까다로운 규칙을 사용하고 싶다면 tslint-config-airbnb 를 설치하고, tslint.json 에서 다음과 같이 적용하시면 됩니다.
$ yarn add --dev tslint-config-airbnb
{
"extends": [
"tslint:recommended",
"tslint-react",
"tslint-config-airbnb",
"tslint-config-prettier"
],
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
},
"rules": {
"import-name": false,
"interface-name": false,
"semicolon": [true, "always", "ignore-bound-class-methods"],
"variable-name": false
}
}
끄고싶은 규칙이 있다면 rules 에서 조정해주시면 됩니다.
airbnb 규칙을 적용하고나면 index.js 와 registerServiceWorker 쪽에서 에러가 발생할텐데요, index.js 에서는 render 가 사용되는 곳을 이렇게 수정해주시고
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root') as HTMLElement);
registerServiceWorker();
registerServiceWorker.js 에서는 코드 최상단에 다음 주석을 작성하여 TSLint 에서 이 파일을 검사하지 않도록 하세요.
/* tslint:disable */
prettier 를 사용하고 싶으시다면 다음과 같이 .prettierrc.js 파일을 만드세요
.prettierrc.js
module.exports = {
printWidth: 100,
parser: 'typescript',
singleQuote: true,
useTabs: false, // Indent lines with tabs instead of spaces.
printWidth: 80, // Specify the length of line that the printer will wrap on.
tabWidth: 2, // Specify the number of spaces per indentation-level.
trailingComma: 'es5'
};
그리고 VSCode 환경설정에서 다음과 같이 해주면 저장할때마다 자동 포맷팅 됩니다.
{
"editor.formatOnSave": true
}
타입 사용하기
우선 src 디렉토리에 tutorial.ts 라는 파일을 만들어서 기본적인 연습을 해봅시다. TypeScript 를 사용 할때는 .ts (리액트 컴포넌트의 경우에는 .tsx) 확장자를 사용합니다.
let, const 사용
let text: string = '';
let number: number = 5;
number = 'asdf';
text = 5;
const array: number[] = [1, 2, 3];
array.push('asdf');
이렇게, 의도치 않은 작업을 하려고 하면 오류 뿜뿜 하는것을 경험해보세요!
이번엔 함수를 작성하보겠습니다.
function squareNumbers(numbers: number[]): number[] {
return numbers.map(n => n * n);
}
숫자 배열을 파라미터로 받아와서 결과값도 숫자배열로 반환합니다.
이렇게 하고나면 내장함수를 사용 할 때에도 해당 값의 타입이 뭔지 추론을 할 수 있습니다. n, numbers, squareNumber 쪽에 마우스를 올려보세요.
그리고, 이런식으로 숫자 배열이 아닌것을 파라미터로 전달하면, 당연히 오류가 나겠죠?
우리가 방금 사용한 타입외에도 수많은 타입들이 있는데요, 여기 에서 확인 가능하니 참고하세요!
리액트 class 형 컴포넌트 만들기
TypeScript React Code Snippets 를 사용하면 tsrcc, tsrcfull, tsrsfc 등의 단축단어로 컴포넌트를 쉽게 생성 할 수 있습니다. 설치하는것을 적극 권장드립니다.
자~ 그러면 무난한 카운터를 만들어 볼까요? 우선 props 만 받아와서 렌더링 해보겠습니다.
src/Counter.tsx
import * as React from 'react';
interface CounterProps {
startNumber: number;
}
class Counter extends React.Component<CounterProps> {
public render() {
return (
<div>
<h1>{this.props.startNumber}</h1>
<button />
</div>
);
}
}
export default Counter;
이제 App 에서 렌더링하겠습니다. 자동완성을 적극적으로 사용해봅시다! Counter 를 렌더링 할 때 상단에서 import 구문 입력하지 말고 JSX 에서 <Counter 를 입력하고 엔터를 누르면 자동으로 불러와질 것입니다. (TypeScript 를 사용하지 않아도 이 기능이 작동하기는 하죠.. 왜냐하면 위에서도 언급했듯이 TypeScript 의 기능이 VSCode 에 기본 탑재가 되어있기 때문입니다.) 추가적으로! props 를 빠트리면 빨간줄이 그어지고, props 를 설정하게 될 때에 startNumber 이름이 자동완성 됩니다.
src/App.tsx
import * as React from 'react';
import Counter from './Counter';
class App extends React.Component {
public render() {
return <Counter startNumber={5} />;
}
}
export default App;
이번에는 Counter 에서 state 를 사용해볼까요?
src/Counter.tsx
import * as React from 'react';
interface CounterProps {
startNumber: number;
}
interface CounterState {
number: number;
}
class Counter extends React.Component<CounterProps, CounterState> {
public state = {
number: 0,
};
constructor(props: CounterProps) {
super(props);
this.state.number = props.startNumber;
}
public handleClick = () => {
this.setState({
number: this.state.number + 1,
});
};
public render() {
return (
<div>
<h1>{this.state.number}</h1>
<button onClick={this.handleClick}>Click Me</button>
</div>
);
}
}
export default Counter;
리액트 함수형 컴포넌트 만들기
함수형 컴포넌트를 만들 땐, 이렇게 만듭니다.
src/ShowName.tsx
import * as React from 'react';
interface ShowNameProps {
name: string;
}
const ShowName: React.SFC<ShowNameProps> = ({ name }) => (
<div>
내 이름은 <b>{name}</b>
</div>
);
export default ShowName;
App 컴포넌트에서 렌더링해보세요!
import * as React from 'react';
import Counter from './Counter';
import ShowName from './ShowName';
class App extends React.Component {
public render() {
return (
<div>
<ShowName name="velopert" />
<Counter startNumber={5} />
</div>
);
}
}
export default App;
이제, 여러분들도 리액트 컴포넌트에서의 기본적인 TypeScript 활용을 할 수 있게 되었습니다. Redux 랑 함께 사용하게 되면 정말 편해지는데, 다음 강의에서 진행하겠습니다!