[TypeScript] 타입스크립트의 타입

Jeris·2023년 6월 7일
0

TypeScript

목록 보기
2/11

기본 타입

기본 타입이란 타입스크립트에서 제공하는 내장 타입을 의미합니다. 다음 그림은 타입스크립트가 제공하는 기본 타입들을 계층에 따라 분류한 타입 계층도 그립입니다.

원시 타입과 리터럴 타입

원시 타입

원시 타입(Primitive Type)은 동시에 한개의 값만 저장할 수 있는 타입들을 말합니다.

  • number: 숫자 타입입니다. 실수, 정수, Infinity, -Infinity, NaN 등 모든 숫자를 나타냅니다.
  • string: 문자열 타입입니다. 문자 또는 문자열을 나타냅니다.
  • boolean: 불리언 타입입니다. 참(true) 또는 거짓(false)만을 나타냅니다.
  • null: Null 타입으로, 값이 없음을 나타냅니다.
  • undefined: Undefined 타입으로, 정의되지 않음을 나타냅니다.

타입스크립트는 변수의 이름 뒤에 콜론(:)과 함께 변수의 타입을 정의하는 타입 주석(type annotation)을 사용하여 변수의 타입을 지정할 수 있습니다.

let num1: number = 123;
let num2: number = Infinity;
let num3: number = -Infinity;
let num4: number = NaN;

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

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

let null1: null = null;

let unde1: undefined = undefined;

// strictNullChecks를 false로 지정하면 다음과 같이 쓸 수 있습니다.
let numA:number = null;

리터럴 타입

리터럴 타입(literal type)은 특정 값에 해당하는 타입을 의미합니다. 특정 숫자, 문자열, 불리언 값에 해당하는 타입이 이에 해당합니다. 예를 들어, '10'이라는 숫자 값에 대한 타입은 '10', 'hello'라는 문자열 값에 대한 타입은 'hello'가 됩니다. 리터럴 타입은 주로 특정 값의 집합만 허용하는 경우에 사용됩니다.

let numA: 10 = 10; // ✅ OK

let strA: "hello" = "hello"; // ✅ OK

let boolA: true = false; // ❌ No, boolA는 true라는 값만 가질 수 있습니다.

배열과 튜플

배열

배열(array)은 여러 값을 단일 변수에 저장할 수 있는 특수한 유형의 데이터 구조입니다.

let numArr: number[] = [1, 2, 3];

let strArr: string[] = ["hello", "im", "winterlood"];

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

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

// 다차원 배열 타입
let doubleArr: number[][] = [
  [1, 2, 3],
  [4, 5],
];

튜플

튜플(tuple)은 길이와 타입이 고정된 배열을 의미합니다. 튜플의 각 요소는 고유한 타입을 가질 수 있습니다.

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

// 컴파일 후에 튜플 타입도 배열로 변환되므로, push나 pop 같은 배열 메서드를 사용할 수는 있습니다.
tup1.push(1);
tup1.pop();

// 튜플이 유용한 상황
const users: [string, number][] = [
  ["이정환", 1],
  ["이아무개", 2],
  ["김아무개", 3],
  ["박아무개", 4],
  [5, "최아무개"], // ❌ No
];

객체

객체(object)는 여러 가지 값(properties)을 하나의 그룹으로 묶어서 관리하는 데이터 타입입니다. TypeScript에서는 객체의 구조를 나타내는 타입을 명시적으로 선언할 수 있습니다. 이렇게 선언된 타입은 객체가 가져야 하는 속성과 그 속성의 타입을 정의합니다.

let user: {
  id: number;
  name: string;
} = {
  id: 1,
  name: "이정환",
};

let dog: {
  name: string;
  color: string;
} = {
  name: "돌돌이",
  color: "brown",
};

이러한 방식으로 구조를 기반으로 타입을 정의하는 것을 구조적 타입 시스템(Structural Type System) 또는 속성 기반 타입 시스템(Property-Based Type System)이라고 합니다. 반면에 이름을 기준으로 타입을 정의하는 것을 명목적 타입 시스템(Nominal Type System)이라고 합니다.

선택적 속성

TypeScript에서는 선택적 속성(Optional Property)을 정의할 수 있습니다. 이는 객체가 해당 속성을 가질 수도 있고, 가지지 않을 수도 있음을 나타냅니다. 속성 이름 뒤에 '?'를 붙여서 선언합니다.

let user: {
  id?: number; // 선택적 속성
  name: string;
} = {
  id: 1,
  name: "이정환",
};

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

읽기 전용 속성

TypeScript는 속성이 객체 생성 후에 변경되지 않아야 함을 지정할 수 있는 읽기 전용(Readonly Property) 속성을 지원합니다.

let config: {
  readonly apiKey: string; // 읽기 전용 속성
} = {
  apiKey: "MY API KEY",
};

config.apiKey = "hacked"; // ❌ No

타입 별칭과 인덱스 시그니쳐

타입 별칭

타입 별칭(Type Alias)은 기존의 타입에 새로운 이름을 부여하는 것으로, 복잡한 타입을 간단하게 표현하거나 보다 직관적으로 이해할 수 있도록 도와줍니다. 이렇게 생성된 별칭은 원래의 타입과 완벽하게 호환됩니다.

// 타입 별칭
type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
};

let user: User = {
  id: 1,
  name: "이정환",
  nickname: "winterlood",
  birth: "1997.01.07",
  bio: "안녕하세요",
  location: "부천시",
};

let user2: User = {
  id: 2,
  name: "이름",
  nickname: "닉네임",
  birth: "1995.04.10",
  bio: "소개",
  location: "위치",
};

인덱스 시그니쳐

인덱스 시그니처(Index Signature)는 객체의 인덱스에 대한 타입을 선언합니다. 인덱스는 객체의 속성 이름을 나타내며, 인덱스 시그니처를 사용하면 동적으로 추가되는 객체의 속성에 대한 타입을 지정할 수 있습니다.

// 인덱스 시그니처
type CountryCodes = {
  [key: string]: string;
};

let countryCodes: CountryCodes = {
  Korea: "ko",
  UnitedStates: "us",
  UnitedKingdom: "uk",
};

type CountryNumberCodes = {
  [key: string]: number;
  Korea: number; // 필수 속성
  UnitedState: string; // ❌ No
};

let countryNumberCodes: CountryNumberCodes = {
  Korea: 410,
  UnitedStates: 840,
  UnitedKingdom: 826,
};

열거형 타입

열거형 타입(Enumerable Type)은 일련의 상수들을 하나의 집합으로 정의하며, 프로그래밍 언어에서 상수를 논리적으로 묶는 방법을 제공합니다. TypeScript에서는 enum 키워드를 사용하여 열거형을 정의합니다.

TypeScript에서는 열거형의 기본 값이 0부터 시작하여 1씩 증가하는 숫자로 설정되며, 이는 명시적으로 다른 값을 설정하지 않는 한 변경되지 않습니다.

// 숫자 열거형
enum Role {
  ADMIN // 0 
  USER = 11, // 11
  GUEST, // 12
}

// 문자열 열거형
enum Language {
  KOREAN = "ko",
  ENGLISH = "en",
}

열거형을 사용하면, 코드의 가독성을 향상시키고 실수를 줄일 수 있습니다. 예를 들어, 사용자의 역할이나 언어 설정 등을 열거형으로 정의할 수 있습니다.

const user1 = {
  name: "이정환",
  role: Role.ADMIN, // 0
  language: Language.KOREAN, // "ko"
};

const user2 = {
  name: "홍길동",
  role: Role.USER, // 11
  language: Language.ENGLISH, // "en"
};

열거형 타입은 컴파일시에 자바스크립트 객체로 변환됩니다. 이는 열거형이 런타임에 존재하며, 열거형의 특성을 유지하고 사용하는 데 도움이 됩니다.

var Role;
(function (Role) {
    Role[Role["ADMIN"] = 0] = "ADMIN";
    Role[Role["USER"] = 1] = "USER";
    Role[Role["GUEST"] = 2] = "GUEST";
})(Role || (Role = {}));
var Language;
(function (Language) {
    Language["korean"] = "ko";
    Language["english"] = "en";
    Language["japanese"] = "jp";
})(Language || (Language = {}));
  

any와 unknown

any 타입은 TypeScript에서 모든 종류의 값들을 할당받을 수 있는 가장 유연한 타입입니다. 이 타입은 TypeScript에서 모든 연산을 허용하므로, 특정 변수의 타입을 확실히 알 수 없을 때에만 사용하는 것이 좋습니다.

let anyVar: any = 10;
anyVar = "hello";
anyVar = true;
anyVar = {};
anyVar = () => {};

// anyVar는 모든 종류의 연산이 가능합니다.
anyVar.toUpperCase();
anyVar.toFixed();

let num: number = 10;
num = anyVar; // ✅ OK

하지만 any 타입은 타입 검사의 편리함을 희생하므로, 이 타입을 과도하게 사용하면 코드의 안정성이 떨어질 수 있습니다. 그래서 가능한 한 any 타입의 사용을 최소화하는 것이 좋습니다.

반면에, unknown 타입은 변수가 어떤 타입의 값을 가질지 미리 알 수 없을 때 사용합니다. unknown 타입은 any와 비슷하게 어떤 값이든 할당받을 수 있지만, any와는 달리 unknown 타입의 값은 다른 타입의 변수에 바로 할당할 수 없으며, 연산에도 참여할 수 없습니다. 즉, unknown 타입의 값은 명시적으로 타입 체크를 해주어야만 다른 변수에 할당하거나 연산에 사용할 수 있습니다.

let unknownVar: unknown;

unknownVar = "";
unknownVar = 1;
unknownVar = () => {};

num = unknownVar; // ❌ No: unknown 타입은 다른 타입에 할당할 수 없음

unknownVar.toUpperCase(); // ❌ No: unknown 타입은 연산에 참여할 수 없음

// 타입 좁히기를 통해 안전하게 값을 할당할 수 있습니다.
if (typeof unknownVar === "number") {
  num = unknownVar;
}

void와 never

void 타입은 아무런 값도 갖지 않음을 나타내는 타입입니다. 이 타입은 주로 아무런 값을 반환하지 않는 함수의 반환 타입으로 사용됩니다.

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

let a: void;

// void 타입 변수에는 null 또는 undefined만 할당할 수 있습니다.
a = 1; // ❌ No: '1' 형식은 'void' 형식에 할당할 수 없습니다.
a = "hello"; // ❌ No: '"hello"' 형식은 'void' 형식에 할당할 수 없습니다.
a = {}; // ❌ No: '{}' 형식은 'void' 형식에 할당할 수 없습니다.
a = null; // ❌ No: strict 모드에서는 null도 허용되지 않습니다.
a = undefined; // ✅ OK

never 타입은 어떤 타입도 할당할 수 없는 특별한 타입입니다. 이 타입은 함수가 절대 반환하지 않는 경우나 불가능한 코드 경로를 나타낼 때 사용됩니다.

function func3(): never {
  while (true) {}  // 무한 루프로 인해 반환되지 않는 함수
}

function func4(): never {
  throw new Error();  // 항상 오류를 던지므로 반환되지 않는 함수
}

let a: never;

// never 타입 변수에는 어떤 값도 할당할 수 없습니다.
a = 1; // ❌ No: '1' 형식은 'never' 형식에 할당할 수 없습니다.
a = {}; // ❌ No: '{}' 형식은 'never' 형식에 할당할 수 없습니다.
a = ""; // ❌ No: '""' 형식은 'never' 형식에 할당할 수 없습니다.
a = undefined; // ❌ No: 'undefined' 형식은 'never' 형식에 할당할 수 없습니다.
a = null; // ❌ No: 'null' 형식은 'never' 형식에 할당할 수 없습니다.

이처럼 never 타입은 코드에서 절대로 발생하지 않아야 하는 상황을 나타내거나, 항상 오류를 던지거나 절대 반환하지 않는 함수의 반환 타입으로 사용됩니다.


Reference

profile
job's done

0개의 댓글