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 를 쓸 줄 아는 리액트 개발자가 되었습니다!

results matching ""

    No results matching ""