타입스크립트 기본 - (2)

DevSeong2·2023년 9월 8일
0

TypeScript

목록 보기
3/4
post-thumbnail

"한 입 크기로 잘라먹는 타입스크립트 - 인프런" 강의를 요약하고 덧붙인 글입니다.

타입 별칭(Type Alias)

객체 타입을 사용할 때 직접 해당 타입을 명시하였습니다.

let user: {
  id: number;
  name: string;
} = {
  id: 1,
  name: "cs",
}

그런데 같은 타입의 변수를 여러개 사용해야 한다면 아래와 같이 타입을 중복해서 정의해서 사용하게 됩니다.

let user1: {
  id: number;
  name: string;
} = {
  id: 1,
  name: "cs",
}

let user2: {
  id: number;
  name: string;
} = {
  id: 2,
  name: "jh",
}

타입 정의는 중복되는 코드입니다. 이는 유지보수를 어렵게 하고 가독성을 떨어트립니다. 그리고 때로는 같은 타입을 재사용하거나 또 다른 이름으로 부르고 싶은 경우도 존재합니다. 타입 별칭을 사용하면 변수를 선언하듯 타입을 별도로 정의할 수 있습니다.
type 타입_이름 = 타입 형태로 타입을 정의합니다.

type User = {
  id: number;
  name: string;
}

let user: User = {
  id: 1,
  name: "cs",
}

단, 동일한 스코프에 동일한 이름의 타입 별칭을 선언하는 것은 불가능합니다.
타입 별칭은 타입 관련 문법이기 때문에 컴파일 결과 사라집니다.

인덱스 시그니처(Index Signature)

인덱스 시그니쳐는 객체 타입을 유연하게 정의할 수 있도록 돕는 특수한 문법입니다. 예를 들어 다양한 국가들의 영어 코드를 저장하는 객체가 있다고 가정하겠습니다.

type CountryCodes = {
  Korea: string;
  UnitedState: string;
  UnitedKingdom: string;
};

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

countryCodes에 100개의 프로퍼티가 추가되어야 한다면 타입 정의에도 각 프로퍼티를 모두 정의해주어야 합니다. 아직 추가되어야 할 프로퍼티 이름과 값을 알지 못한다면 타입을 정의하지 못할 것입니다.
하지만 객체의 키와 값의 타입 규칙을 알고 있다면 인덱스 시그니쳐를 이용하여 간단하게 타입을 정의할 수 있습니다.

type CountryCodes = {
  [key: string]: string;
};

let countryCodes: CountryCodes = {
  Korea: "ko",
  UnitedState: "us",
  UnitedKingdom: "uk",
  // (... 약 100개의 국가)
  Brazil : 'bz'
};

[key : string] : string, 키가 string 타입이고 값이 string 타입인 모든 프로퍼티를 포함한다는 의미입니다.

주의할 점은 인덱스 시그니쳐는 정의된 규칙을 위반하지만 않으면 모든 객체를 허용합니다. 빈 객체는 이를 위반하지는 않으므로 오류가 발생하지 않습니다.

type CountryNumberCodes = {
  [key: string]: number; 
}

let countryNumberCodes: CountryNumberCodes = {}; // ⭕

반드시 포함해야 하는 프로퍼티가 있다면 직접 명시할 수 있습니다. 단, 인덱스 시그니쳐의 value 타입과 직접 추가한 프로퍼티의 value 타입이 호환되거나 일치해야 합니다.

type CountryNumberCodes = {
  [key: string]: number;
  Korea: number; // Korea: string; ❌ Error
}

열거형(Enum) 타입

여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입입니다. 열거형 타입은 자바스크립트에는 존재하지 않고 오직 타입스크립트에서만 사용할 수 있는 특별한 타입입니다.

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

const user1 = {
  name: "admin",
  role: Role.ADMIN,
}

enum 멤버에 숫자 값을 직접 할당하지 않아도 0부터 1씩 늘어나는 값으로 자동 할당됩니다.

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

자동 할당되는 값은 기본적으로 0부터 시작합니다. 만약 이 값을 변경하고 싶다면 다음과 같이 시작하는 위치에 값을 직접 할당해주면 됩니다. 그럼 자동으로 그 아래의 멤버들은 1씩 증가된 값으로 할당됩니다.

enum Role {
  ADMIN = 10, // 10 
  USER,  // 11 
  GUEST, // 12
}

enum의 멤버에는 숫자 말고도 문자열 값도 할당할 수 있습니다. 숫자형 열거형에서 숫자만으로는 그 값이 어떤 의미인지 추측하기 힘들 때 프로퍼티의 값으로 사용할 수 있습니다.

enum Role {
  ADMIN,
  USER,
  GUEST,
}

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

const user1 = {
  name: "admin",
  role: Role.ADMIN, // 0
  language: Language.korean,// "ko"
};

enum의 컴파일 결과

타입스크립트 타입 관련 코드는 컴파일할 때 사라집니다. 그런데 enum은 컴파일될 때 다른 타입들처럼 사라지지 않고 자바스크립트 객체로 변환됩니다.

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 || (Language = {}));
var user1 = {
    name: 'admin',
    role: Role.ADMIN,
    language: Language.korean,
};

(+) enum을 사용하지 말아야 하는 이유

Any 타입

any 타입은 특정 변수의 타입을 모를 때, 특정 값으로 인하여 타입 검사 오류가 발생하는 것을 원하지 않을 때 사용할 수 있습니다.

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

anyVar = true;
anyVar = {};

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

any 타입은 어떠한 타입 검사도 받지 않기 때문에 아무 타입의 값이나 담아 사용할 수 있고 또 다양한 타입의 메서드도 마음대로 호출해서 사용해도 문제가 되지 않습니다.

그러나, 위의 코드를 실행해보면 런타임에 오류가 발생합니다. 타입스크립트를 쓰는 이유는 실행 전에 타입 오류를 검사해서 안정성이 높이는 것인데 any 타입은 타입 검사를 받지 않으므로 타입스크립트를 쓰는 의미가 없습니다.

그렇다면 값의 타입을 모르는 경우는 어떻게 해야할까요? unknown 타입을 사용합니다.

Unknown 타입

unknown 타입은 어떤 타입의 값이든 다 저장할 수 있습니다. any 타입과 비슷하지만 unknown 타입은 어떤 타입의 변수에도 저장할 수 없습니다.

let num: number = 10;

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

num = unknownVar; // ❌ Error

또 unknown 타입의 값은 어떤 연산에도 참여할 수 없으며, 어떤 메서드도 사용할 수 없습니다. 어떤 변수가 어떤 값을 받게 될 지 모른다면 any 타입보다 unknown 타입을 사용하는게 더 안전합니다.

let unknownVar: unknown;
unknownVar * 2 // ❌ Error

연산을 수행하고 싶다면 조건문을 이용해 이 값이 특정 타입임을 보장해주면 됩니다.

if (typeof unknownVar === "number") {
  unknownVar * 2;
}

Void 타입

void 타입은 아무런 값도 없음을 의미하는 타입입니다. 함수에 return문이 없거나 명시적으로 값을 반환하지 않을 때 추론되는 타입입니다.

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

function func2() {
  return;
}

함수의 void 반환 타입

타입스크립트에서 void 타입을 사용하더라도 값을 반환할 수 있습니다. void를 반환 타입으로 사용했을 때입니다.

void 반환 타입으로의 문맥적 타이핑은 함수를 아무것도 반환하지 않도록 강제하지 않습니다.이를 설명하는 또 다른 방법은, void 반환 타입을 가지는 문맥적 함수 타입(type vf = () => void)가 구현되었을 때, 아무값이나 반환될 수 있지만, 무시됩니다. - TypeScript: Documentation

void 반환 타입은 함수가 다른 값을 반환할 수도 있다는 것입니다. 아래의 코드들은 모두 유효한 코드입니다.

type voidFunc = () => void;
 
const f1: voidFunc = () => {
  return true;
};
 
const f2: voidFunc = () => true;
 
const f3: voidFunc = function () {
  return true;
};

자세한 내용은 ➡ 왜 TypeScript는 void 타입을 사용해도 값을 return 할 수 있을까?

Never 타입

never 타입은 결코 관측될 수 없는 값을 의미합니다. 보통 다음과 같이 함수가 어떠한 값도 반환할 수 없는 상황(무한 루프, 프로그램 실행 종료, 오류 발생 등)에 해당 함수의 반환값 타입을 정의할 때 사용됩니다.

function func3(): never {
  while (true) {}
}

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

void와 혼동하기 쉬우나 분명한 차이가 있습니다. void 타입은 아무것도 반환하지 않습니다. never 타입은 결코 반환값이 있을 수 없는, 반환이 '불가능'함을 나타내는 타입입니다.

변수의 타입을 never로 정의하면 어떠한 타입의 값도 변수에 담을 수 없습니다.

let anyVar: any;

let a: never;
a = 1; // ❌
a = null; // ❌
a = undefined; // ❌
a = anyVar; // ❌

추가로 읽을거리 타입스크립트의 Never 타입 완벽 가이드

Reference

profile
차근차근

0개의 댓글