8 분 소요

타입스크립트 기본 타입

기본 타입이란 타입스크립트가 자체적으로 제공하는 타입(내장 타입)을 말한다. 다음 그림은 타입스크립트가 제공하는 기본 타입을 계층에 따라 분류한 타입 계층도이다.


그림에서 볼 수 있다시피 타입스크립트에는 많은 기본 타입들이 제공된다. 그리고 이러한 기본 타입들은 서로 부모-자식 관계를 이루며 계층을 형성한다. 우선, string, number와 같은 원시타입들에 대해 먼저 살펴보자.

원시타입 (Primitive Type)

원시 타입(Primitive Type)은 ‘동시에 한 개의 값만 저장할 수 있는 타입’을 말한다. 예를 들어, 배열이나 객체 같은 비원시 타입은 동시에 여러 값들을 저장할 수 있는 반면, number, string, boolean 등의 원시 타입은 숫자면 숫자, 문자열이면 문자열과 같이 딱 하나의 값만 저장할 수 있다.

number 타입

number 타입은 자바스크립트에서 숫자를 의미하는 모든 값을 포함하는 타입이다. 단순 정수 뿐만 아니라 소수, 음수, Infinity, NaN 등의 특수한 숫자들도 포함한다.

// number
let num1: number = 123;
let num2: number = -123;
let num3: number = 0.123;
let num4: number = -0.123;
let num5: number = Infinity;
let num6: number = -Infinity;
let num7: number = NaN;

number 타입으로 정의한 변수에는 number 타입을 제외한 값을 할당할 수 없으며, number 타입의 값이 사용할 수 없는 toUpperCase와 같은 메소드는 사용할 수 없다.

string 타입

string 타입은 문자열을 의미하는 타입이다. 단순 쌍따옴표 문자열 뿐만 아니라 작은 따옴표, 백틱, 템플릿 리터럴로 만든 모든 문자열을 포함한다.

// string
let str1: string = "hello";
let str2: string = "hello";
let str3: string = `hello`;
let str4: string = `hello ${str1}`;

boolean 타입

boolean 타입은 참과 거짓만을 저장하는 타입이다. true 또는 false만 이 타입에 해당된다.

// boolean
let bool1: boolean = true;
let bool2: boolean = false;

null 타입

null타입은 오직 null값만 포함하는 타입이다.

// null
let null1: null = null;

undefined 타입

undefined타입은 null타입과 마찬가지로 오직 하나의 값, undefined만 포함하는 타입이다.

리터럴 타입

타입스크립트에는 앞서 배운 string, number 처럼 범용적으로 많은 값을 포함하는 타입 뿐만 아니라 딱 하나의 ‘값’만 포함하는 타입도 존재한다.

let numA: 10 = 10;

이처럼 변수 numA의 타입을 10이라는 숫자값으로 설정할 경우, numA에는 10 이외의 값을 저장할 수 없게 된다.

이처럼 하나의 값만 포함하도록 값 자체로 만들어진 타입을 타입스크립트에서는 ‘리터럴 타입’이라고 부른다. 좀 더 구체적으로, ‘스트링 리터럴 타입’, ‘넘버 리터럴 타입’ 등과 같이 해당 리터럴의 타입과 함께 붙여서 부르기도 한다.

비원시 타입 (Non-primitive Type)

배열 타입

타입스크립트에서는 다음과 같이 배열 타입을 정의한다.

let numArr: number[] = [1, 2, 3];
let strArr: string[] = ["hello", "world"];

혹은 다음과 같은 형식으로도 배열의 타입을 정의할 수 있다.

let boolArr: Array<boolean> = [true, false, true];

이처럼 꺽쇠<>와 함께 타입을 작성하는 문법을 ‘제네릭’이라고 하는데, 이는 추후에 다룰 예정이다. 아무튼 위 두 형식 모두 모양만 다를 뿐 기능은 동일하다. 다만 특수한 상황이 아닐 경우 좀 더 타이핑하기 쉽고 이해하기 쉬운 첫 번째 방식으로 배열 타입을 정의하는 것을 추천한다.

만약 여러 타입의 요소를 갖는 배열을 정의할 경우에는 소괄호와 바(|)를 이용해 배열 요소가 둘 중 하나의 타입에 해당하도록 타입을 정의하면 된다.

let multiArr: (number | string)[] = [1, "hello"];

위 코드에서 (number | string)을 통해 배열 내부의 요소의 타입을 명시해주었고, []를 통해 배열임을 나타내주었다.

이렇게 바(|)를 이용해 여러 타입 중 하나를 만족하는 타입을 정의하는 문법을 유니온(Union) 타입이라고 부른다. 이를 여러 타입 중 하나를 만족하는 경우 허용하는 범용적인 타입을 만들 수 있다.

다차원 배열

다음과 같이 []를 연달아 작성해 다차원 배열의 타입도 간단하게 정의할 수 있다.

let doubleArr: number[][] = [
  [1, 2, 3],
  [4, 5],
];

튜플 타입

튜플은 자바스크립트에는 없는 타입으로, 길이와 타입이 고정된 배열을 의미한다.

예를 들어, 길이가 2로 고정된 2개의 number 타입 요소를 갖는 튜플(배열) 타입은 다음과 같이 정의한다.

let tup1: [number, number] = [1, 2];

또는 다음과 같이 다양한 타입을 갖는 튜플 타입도 정의할 수 있다.

let tup2: [number, string, boolean] = [1, "hello", true];

튜플도 결국 배열이다

tsc를 이용해 튜플 타입이 정의된 타입스크립트 코드를 컴파일 해 보면 결국 튜플은 자바스크립트 배열로 변환되는 것을 확인할 수 있다. 자바스크립트에는 없는 타입이기 때문이다. 그러므로 길이가 고정된 튜플이라도 배열 메소드인 pushpop을 이용해 고정된 길이를 무시하고 요소를 추가하거나 삭제할 수 있다. 따라서 튜플을 사용할 때에는 최대한 배열 메소드를 이용해 요소를 추가하거나 삭제하는 등의 연산을 주의해야 한다.

객체 타입

타입스크립트에서는 2가지 방식으로 객체의 타입을 정의할 수 있다.

  1. 객체를 의미하는 object 타입으로 정의한다.
let user: object = {
  id: 1,
  name: "정주노",
};

하지만 이처럼 정의할 경우 user.id처럼 점 표기법으로 객체의 특정 프로퍼티에 접근하려고 하면 오류가 발생한다. 그 이유는 object 타입은 단순 값이 객체임을 표현하는 것 외에는 아무런 정보를 제공하지 않는 타입이기 때문이다.

따라서, user에 저장된 객체의 구조를 그대로 타입으로 만들고 싶은 경우, 객체의 프로퍼티에 정상적으로 접근하고 싶을 경우에는 객체 리터럴 타입을 사용해야 한다.

  1. 객체 리터럴 타입

객체 리터럴 타입은 다음과 같이 중괄호를 열고 객체가 갖는 프로퍼티를 직접 나열해 만드는 타입이다.

let user: {
  id: number;
  name: string;
} = {
  id: 1,
  name: "정주노",
};

console.log(user.id); // 1

이처럼 변수의 타입을 객체 리터럴 타입으로 정의하면 타입 내에 정의되어 있는 프로퍼티에 이상 없이 접근할 수 있게 된다.

이처럼 타입스크립트는 객체의 타입을 정의할 때 프로퍼티(id, name, …)을 기준으로 객체의 구조를 정의하듯 타입을 정의한다. 이러한 타입스크립트의 특징을 구조적 타입 시스템이라고 한다. 즉, 객체의 구조를 결정하는 것은 프로퍼티이다. 즉, 타입스크립트는 “user 타입 객체에는 number인 id와 string인 name이라는 프로퍼티가 있어야 해.” 라며 객체의 타입을 정의하는 것이다.

특수한 프로퍼티

자바스크립트에서 객체를 다루다 보면 특정 프로퍼티는 있어도 되고 없어도 되는 그런 상황이 존재한다. 이렇게 특정 프로퍼티를 상황에 따라 생략할 수 있게 만들고 싶은 경우 해당 프로퍼티를 선택적 프로퍼티(Optional Property)로 설정해주면 된다. 선택적 프로퍼티로 설정하기 위해선 프로퍼티의 이름 뒤에 ?를 붙여주면 된다.

let user: {
  id?: number; // 선택적 프로퍼티가 된 id
  name: string;
} = {
  id: 1,
  name: "정주노",
};

user = {
  name: "홍길동",
};

만약 특정 프로퍼티를 읽기 전용으로 만들어 값을 수정할 수 없도록 만들 경우, 해당 프로퍼티를 읽기전용 프로퍼티(Readonly Property)로 설정해 해결할 수 있다. 읽기전용 프로퍼티는 프로퍼티의 이름 앞에 readonly 키워드를 붙여주면 된다.

let user: {
  id?: number;
  readonly name: string; //  Readonly 프로퍼티
} = {
  id: 1,
  name: "정주노",
};

user.name = "홍길동"; // Error

타입스크립트의 특별한 타입

열거형 타입 (Enum)

열거형 타입(enum)은 자바스크립트에는 존재하지 않고 오직 타입스크립트에서만 사용할 수 있는 특별한 타입이다.

열거형은 다음과 같이 여러개의 값을 나열하는 용도로 사용한다.

아래는 3개의 멤버 ADMIN, USER, GUEST를 나열한 열거형 타입이다.

enum Role {
  ADMIN,
  USER,
  GUEST,
}

enum의 각 멤버에는 다음과 같이 숫자를 할당할 수 있다.

enum Role {
  ADMIN = 0,
  USER = 1,
  GUEST = 2,
}

이를 활용하여 enum의 멤버들을 값으로 활용하면 다음과 같다.

// enum 타입
// 여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입

enum Role {
  ADMIN = 0,
  USER = 1,
  GUEST = 2,
}

const user1 = {
  name: "정주노",
  role: Role.ADMIN, //관리자, 0
};

const user2 = {
  name: "주노정",
  role: Role.USER, // 회원, 1
};

const user3 = {
  name: "쥬우노",
  role: Role.GUEST, // 게스트, 2
};

이렇게 유저의 권한과 같은 여러개의 멤버를 같는 값을 숫자로 표기할 때 enum을 활용해 보다 안전하고 직관적으로 관리할 수 있다.

참고로, enum 멤버에 숫자 값을 직접 할당하지 않아도 알아서 처음값을 0으로, 밑으로 1씩 늘어나는 값으로 자동으로 할당된다. 만약 이 자동 할당 값을 변경하고 싶다면 다음과 같이 시작하는 위치에 값을 직접 할당해주면 된다.

enum Role {
  ADMIN = 10, // 10 할당
  USER, // 11 할당(자동)
  GUEST, // 12 할당(자동)
}

또한, 이렇게 멤버의 값이 모두 숫자인 enum을 숫자 열거형 타입이라고 부른다.

문자열 열거형

enum의 멤버에는 숫자 말고도 문자열 값도 할당할 수 있다.

예를 들어, 다음과 같이 국가별 언어를 열거하는 enum을 정의할 수 있다.

enum Language {
  korean = "ko",
  english = "en",
}

➡️ enum은 컴파일 결과 객체가 된다 !

enum은 자바스크립트 코드로 컴파일 될 때 다른 타입들처럼 사라지지 않고 자바스크립트 객체로 변환된다. 그렇기 때문에 우리가 위에서 사용했던 것처럼 값으로 사용할 수 있는 것이다.

any 타입

any 타입은 타입스크립트에서만 제공되는 특별한 타입으로, 타입 검사를 받지 않는 타입이다. 따라서 아무 타입의 값이나 범용적으로 할당받을 수 있고, 어떤 타입으로 정의된 변수이던 간에 모두 다 할당할 수 있다. 또 다양한 타입의 메소드도 마음대로 호출해서 사용해도 오류가 발생하지 않는다. 즉, 완전한 치트키 타입이라고 할 수 있다.

let anyVar: any = 10;
anyVar = "hello";

anyVar = true;
anyVar = {};

anyVar.toUpperCase();
anyVar.toFixed();
anyVar.a;

하지만, 그렇기 때문에 any는 최대한 사용하지 말아야 한다.

any로 작성해 논리적으로 오류가 있지만, 타입스크립트 오류가 발생하지 않은 코드를 컴파일 하면 당연하게도 런타임 오류가 발생한다. 이렇듯 any 타입을 많이 사용하면 많은 부분에서 타입 검사가 제대로 이루어지지 않기 때문에 위험한 코드가 생산된다. 즉, 타입스크립트를 사용하는 이유 자체가 사라지게 된다.

unknown 타입

unknown타입은 any타입과 비슷하지만 보다 안전한 타입이다.

unknown 타입의 변수는 어떠한 타입의 값이든 다 저장할 수 있지만, 그 반대는 아니다. unknown타입의 값은 어떤 타입의 변수에도 저장할 수 없다. 또한 어떤 연산에도 참여할 수 없으며, 어떠한 메소드도 사용할 수 없다.

let unknownVar: unknown;
unknownVar = "";
unknownVar = 1;
unknownVar = () => {};

let num: number = 10;
num = unknownVar; // Error
unknownVar * 2; // Error

정리하자면 unknown타입은 모든 값을 할당받을 수 있지만 어떤 타입의 변수에도 할당할 수 없고, 모든 연산에 참가할 수 없기 때문에 오직 값을 정리하는 행위밖에 할 수 없다.

만약 unknown타입의 값을 특정 타입의 값처럼 취급하고 곱셈 연산을 수행하게 하고 싶다면 다음과 같이 조건문을 이용해 unknown의 값이 해당 값임을 보장해주어야 한다. 아래의 예시를 살펴보자.

if (typeof unknownVar === "number") {
  // 이 조건이 참이된다면 unknownVar는 number 타입으로 볼 수 있음
  unknownVar * 2;
}

이와 같이 조건문을 이용해 특정 값이 특정 타입임을 보장할 수 있게 되면 해당 타입의 값이 보장된 타입의 값으로 자동으로 바뀌게 된다. 이를 타입 좁히기라고 한다.

void 타입

void 타입은 아무런 값도 없음을 의미하는 타입이다.

보통은 다음과 같이 아무런 값도 반환하지 않는 함수의 반환값 타입을 정의할 때 사용한다.

function func2(): void {
  console.log("hello");
}

물론 변수의 타입으로도 void 타입을 지정할 수 있다. 하지만 이렇게 정의한 변수는 undefined 이외의 다른 타입의 값은 담을 수 없다. 이유는 void 타입이 undefined 타입을 포함하는 타입이기 때문이다.

let a: void;
a = undefined;

never 타입

never타입은 불가능을 의미하는 타입이다.

보통 다음과 같이 함수가 어떠한 값도 도저히 반환할 수 없는 상황일 때, 값을 반환하면 비정상인 상황일 대 해당 함수의 반환값 타입을 정의할 때 사용된다.

function func3(): never {
  while (true) {} // 여기서 뭔가 반환되는 게 이상함..
}

이처럼 함수가 아무런 값도 반환할 수 없고 뭔가를 반환하는 것 자체가 불가능할 때 반환값의 타입을 never로 정의한다.

무한 루프 외에도 다음과 같이 의도적으로 오류를 발생시키는 함수도 never 타입을 정의할 수 있다.

function func4(): never {
  throw new Error();
}

변수의 타입을 never로 정의하면 any를 포함한 그 어떠한 타입의 값도 담을 수 없게 된다.

let a: never;

a = 1; // Error
a = null; // Error
a = undefined; // Error
let anyVar: any;
a = anyVar; // Error

출처

위 포스트는 이정환 님의 인프런: 한 입 크기로 잘라먹는 타입스크립트(TypeScript)를 수강한 뒤 복습 차원에서 저의 생각을 정리 및 추가하여 업로드했음을 알립니다.

댓글남기기