TIL 72 | TypeScript Basics

meow·2020년 10월 29일
7

TypeScript

목록 보기
1/5

이재승님의 실전 리액트 프로그래밍을 읽고 정리한 내용입니다.

타입스크립트란?

타입스크립트는 자바스크립트의 모든 기능을 포함하면서 정적 타입을 지원하는 언어다. 동적 타입 언어와 정적 타입 언어는 장단점이 있기 때문에 프로젝트의 성격에 따라 선택한다. 큰 규모의 프로젝트에서는 정적 타입의 언어를 사용하는 것을 추천한다.

왜?

정적 타입의 언어의 코드는 타입으로 서로 연결되어 있기 때문에 연관된 코드 간의 이동이 쉽고, 변수명이나 함수명을 변경하는 등의 리팩토링이 쉽다.

타입스크립트는 마이크로소프트에서 개발하고 있고 꾸준하게 업데이트 버전이 나오고 있으며, 리액트 개발자들의 의견을 잘 반영해주기 때문에 JSX 문법과 리액트 컴포넌트의 타입을 정의하는데 큰 어려움이 없다.

타입스크립트의 특징

let v1 = 123;
v1 = 'abc'; // 타입 에러

타입이 숫자인 변수에 스트링을 입력하면 컴파일 타임 에러가 발생한다. 코드를 실행하기 전에 오류를 발견할 수 있다. 이렇게 자동으로 타입을 인지하는 기능을 타입 추론이라고 한다.

타입스트립트에서 타입을 선언하기

타입스크립트에서는 변수의 타입이 숫자이거나, 문자열일 수 있는 경우 다음과 같이 타입 정보를 입력해주어야 한다.

let v1: number | string = 123;
v1 = 'wow'

타입스크립트의 여러가지 타입

const age: number = 27;
const isOld: boolean = age >= 40;
const msg: string = isOld ? '많다' : '적다';

자바스크립트에서 사용했던 타입들을 동일하게 타입스크립트에서 사용하는 것을 확인할 수 있다.

const values: number[] = [1, 2, 3];
const values2: Array<number> = [1, 2, 3];

배열 타입은 위 두가지 방법으로 정의할 수 있다. 둘 다 동일하다.

values.push('a'); // 타입 에러
const data: [string, number] = [msg, age];
data[0].substr(1);
data[1].substr(1);	// 타입 에러

두 번째 엘리먼트의 타입은 문자열이 아닌 숫자이기에 문자열 메소드인substr를 호출하면 타입 에러가 발생한다.

nullundefined 타입

자바스크립트에서는 값으로 존재하는 nullundefined는 타입스크립트에서는 각각 타입으로 존재한다.

let v1: undefined = undefined;
v1 = "wow"; // 타입 에러

let v2: number | undefined = undefined;
v2 = 801;

문자 리터럴과 숫자 리터럴 타입

let v1: 10 | 20 | 30;
v1 = 10;
v1 = 15; // 타입 에러
let v2: '엄마' | '아빠';
v2 = '형'; // 타입에러

any 타입

이름처럼 모든 종류의 값을 허용하는 타입이다. 숫자, 문자열, 함수까지도 입력될 수 있다. 타입 에러가 나는 경우 임시로 any 타입으로 정의한다. 하지만 any를 남발하면 타입스크립트를 사용하는 의미가 없어지기 때문에 되도록 쓰지 않는 것이 좋다!

voidnever 타입

아무 값도 반환하지 않고 끝나는 함수의 반환 타입은 void 타입으로 정의한다. 예외가 발생해서 비정상적으로 종료되거나 무한 루프 때문에 종료되지 않는 함수의 반환 타입은 never 타입으로 정의한다.

object 타입

자바스크립트의 객체 타입과 동일하다.

let v: object;
v = { name: 'mia' };
console.log(v.age); // 타입 에러

객체 속성에 대한 정보가 없기 때문에 타입 에러가 발생한다. 속성 정보를 포함해서 타입을 정의하기 위해서는 인터페이스(interface)를 사용해야 한다.

교차 타입과 유니온 타입

여러 타입의 교집합과 합집합을 각각 교차 타입( & )과 유니온 타입( | )으로 표현할 수 있다.

let v1 = (1 | 2 | 3) & (3 | 6 | 9);
v1 = 3;
v1 = 1; // 타입 에러

type 키워드로 별칭 주기

타입 별칭은 타입을 선언할 때 편리하게 사용할 수 있다.

type Width = number | string;
let width: Width;
width = 100;
width = '100px';

Width 는 일반적인 타입처럼 사용될 수 있다.

열거형 타입

열거형 타입은 enum 키워드를 사용해서 정의한다. 열거형 타입의 각 원소는 값으로 사용될 수 있고, 타입으로 사용될 수도 있다.

enum Job {
  Police,
  Doctor,
  Developer,
}
const v1: Job = Job.Police;
const v2: Job.Police | Job.Developer = Job.Developer;

명시적으로 원소의 값을 입력해줄 수 있다. 열거형 타입의 첫 번째 원소에 값을 할당하지 않으면 자동으로 0이 할당된다. 각 원소에는 숫자나 문자열을 할당할 수 있는데, 명시적으로 값을 입력하지 않는 경우에는 이전 원소에서 1을 더한 값이 할당된다.

enum Job {
  Police,
  Doctor = 10,
  Developer,
}
console.log(Job.Police, Job.Doctor, Job.Developer) // 0, 10, 11

열거형 타입은 객체로 존재하며 각 원소는 이름과 값이 양방향으로 매핑된다. 이런 특징 때문에 열거형 타입은 객체와 동일하게 다룰 수 있고, 값을 이용해서 이름을 가져올 수도 있다.

enum Job {
  Police,
  Doctor = 10,
  Developer,
}
console.log(Job.Doctor) // 10
console.log(Job['Doctor']) // 10
console.log(Job[10]) // Doctor

열거형 타입의 값에 문자열 할당

enum Language {
  Korean: 'ko',
  Japanese: 'jp',
  English: 'En',
}

열거형 타입의 원소에 문자열을 할당하는 경우에는 단방향으로 매핑된다. 서로 다른 원소의 이름이나 값이 같을 경우 충돌이 발생하는 것을 막기 위해서다.

상수 열거형 타입

열거형 타입은 컴파일 후에도 남아있기 때문에 번들 파일의 크기가 커질 수 있다. 상수 열거형 타입을 사용하면 컴파일 결과에 열거형 타입의 객체를 남겨놓지 않는다.

const enum Language {
  Korean: 'ko',
  Japanese: 'jp',
  English: 'En',
}

위와 같이 타입을 상수로 정의하는 경우 객체를 생성하는 코드가 컴파일 후에 보이지 않는다. 단, 열거형 타입을 상수로 정의하면 열거형 타입의 객체를 사용할 수 없다.

const enum Language {
  Korean: 'ko',
  Japanese: 'jp',
  English: 'En',
}
console.log(getEnumLength(Language)); // 타입 에러

함수 타입

함수 타입을 만들기 위해서는 매개 변수 타입반환 타입이 필요하다. 콜론으로 해당 타입들을 정의할 수 있다.

function getInformation(name: string, age: number): string {
  ...
}

매개 변수 타입 : name: string, age: number
반환 타입 : ): string

const v1: string = getInformation('mia', 26);
const v2: string = getInformation('mia', '26'); // 타입 에러
const v3: number = getInformation('mia', 26); // 타입 에러

변수를 함수 타입으로 정의하기

합수를 저장할 변수의 타입은 화살표 기호를 사용하면 된다. 함수를 구현하는 코드에서는 매개변수 타입과 반환 타입을 작성하지 않아도 된다.

const getInformation: (name: string, age: number) => string = function(name, age) {
  ...
}

선택 매개변수

선택 매개변수는 반드시 입력하지 않아도 되는 매개변수다. 매개변수 이름 오른쪽에 물음표를 입력하면 된다.

function getInformation(name: string, age: number, height?: numebr): string {
  ...
}

❗️선택 매개변수를 입력하는 경우에는 반드시 정의된 타입을 만족하는 값을 입력해야 한다.
❗️선택 매개변수의 오른쪽에 필수 매개변수가 오면 컴파일 에러가 발생한다.
-> 에러 없이 구현하려면 undefined를 입력해야 한다.

매개변수의 기본값 정의

function getInfo(
  name: string,
  age: number = 26,  // 매개변수의 기본값을 정의
  language = "english",
): string {
    ...
}
    
console.log(getInfo('mia'));
console.log(getInfo('mia', 27));
// 둘 다 정상적인 호출
const fun1:(
  name: string,
  age?: number,
  language?: string,
) => string = getInfo;

❗️기본값이 있는 매개변수는 선택 매개변수다.

나머지 매개변수

function getInfo(name: string, ...rest: string[]): string {
	...
}

나머지 매개변수는 배열로 정의할 수 있다.

this 타입

함수의 this 타입을 정하지 않으면 기본적으로 any 타입이 적용된다. 따라서 this 타입을 정의해두는 것이 좋다.

function getParam(index: number): string {
  const params = this.splt(',');
  if (index < 0 || params.length <= index) {
    return '';
  }
  return this.split(',')[index];
}

this 타입이 any가 되었기 때문에 컴파일 에러가 발생하지 않는다. this 타입은 첫번째 매개변수 위치에서 정의할 수 있다.

function getParam(this: string, index: number): string {
  const params = this.splt(','); // 타입 에러
}

❗️index가 두번째 매개변수의 자리에 있기는 하지만 this 타입은 매개변수로 치지 않기 때문에 index가 첫번째 매개변수이다.

원시 타입에 메서드 추가하기

원시 타입에 메서드를 등록할 때는 인터페이스를 사용한다.

interface String { // 문자열타입에 getParam 메서드 추가
  getParam(this: string, index: number): string;
}
String.prototype.getParam = getParam; // 문자열 프로토타입에 함수 등록
console.log('wow, 123, yes'.getParam(1)); // 호출가능!

함수 오버로드: 여러 개의 타입 정의하기

함수 오버로드를 사용하면 하나의 함수에 여러 개의 타입을 정의할 수 있다. add 함수를 만들어 다음과 같은 일을 처리한다고 가정해보자.

  • 두 매개변수가 모두 문자열 -> 문자열 반환
  • 두 매개변수가 모두 숫자 -> 숫자 반환
  • 두 매개변수가 서로 다른 타입이면 안된다.

함수 오버로드를 사용하지 않을 때

function add(x: number | string, y: number | string): number | string {
  if (typeof x === 'number' && typeof y === 'number') {
      return x + y;
    } else {
      const result = Number(x) + Number(y);
      return result.toString();
    }
}
const v1: number = add(1, 2); // 타입 에러?
console.log(add(1, '2'));

엥... 모든 매개변수가 숫자면 반환값도 숫자가 될거라 생각하지만 타입 에러가 발생한다. 두 매개 변수의 타입이 달라도 타입 에러는 발생하지 않는다. 함수의 타입을 구체적으로 정의하지 못했기 때문이다. 이런 상황일때 함수 오버로드를 사용한다.

  • RunJS에서 실행해보니 const v1: number = add(1, 2); 이 부분이 에러 없이 잘 작동한다. 원인은 모르겠다.. 개선된건가?

함수 오버로드를 사용할 때

function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: number | string, y: number | string): number | string {
	...
}
console.log(add(1, 2)); // 타입 에러

이 부분 이해가 잘 되지 않는다 ㅠㅠ... 이해하면 추가하기로

+)
아래 예제에서 add 함수는 2개의 선언부와 1개의 구현부를 가지고 있다.
주의할 점은 함수 선언부와 구현부의 매개변수 개수가 같아야 한다.

함수 구현부에 any가 자주 사용된다.

명명된 매개변수

function getInfo({
  name,
  age = 26, // 기본값이 있다면 여기서 같이 정의한다.
  language, // 매개변수의 이름 정의
}: {
  name: string;
  age?: number;
  language?: string; // 매개변수의 타입 정의
}): string {
  const nameText = name.substr(0, 10);
  const ageText = age > 40 ? 'senior' : 'junior';
  return `name: ${nameText}, age: ${ageText}, language: ${language}`;
}

명명된 매개변수의 타입을 재사용하고 싶다면? 인터페이스를 사용한다!

interface Param {
  name: string;
  age?: number;
  language?: string; // 매개변수의 타입을 인터페이스로 정의
}
function getInfo({ name, age = 26, language }: Param): string {
	... // Param 인터페이스를 사용한다
}

내일은 인터페이스 조지기로.... 다짐.....

profile
🌙`、、`ヽ`ヽ`、、ヽヽ、`、ヽ`ヽ`ヽヽ` ヽ`、`ヽ`、ヽ``、ヽ`ヽ`、ヽヽ`ヽ、ヽ `ヽ、ヽヽ`ヽ`、``ヽ`ヽ、ヽ、ヽ`ヽ`ヽ 、ヽ`ヽ`ヽ、ヽ、ヽ`ヽ`ヽ 、ヽ、ヽ、ヽ``、ヽ`、ヽヽ 🚶‍♀ ヽ``ヽ``、ヽ`、

2개의 댓글

comment-user-thumbnail
2020년 11월 2일

지나가다가 타입 스크립트의 중요성을 잘 알고 갑니다.

1개의 답글