[TypeScript] 섹션3. 타입스크립트 기본(2) ~ 섹션4. 타입스크립트 이해하기(1)

jaehoon ahn·2025년 2월 3일

TypeScript

목록 보기
4/14
post-thumbnail

섹션3. 타입스크립트 기본

배열과 튜플

배열

// 배열
let numArr: number[] = [1, 2, 3];
let strArr: string[] = ["hello", "im", "zeno"];

let boolArr: Array<boolean> = [true, false, true]; // 제네릭 문법

.
.
let 배열명: 타입 [] = [];

다차원 배열의 타입을 정의하는 방법

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

튜플

// 튜플 js (X), ts에만 제공됌
// 튜플: 길이와 타입이 고정된 배열
let tup1: [number, number] = [1, 2];
// tup1 = [1, 2, 3]; -> 길이가 넘어서거나
// tup1 = ["1", "2"] -> 타입을 다르게해서 바꿀 수 없다.

주의사항1


let tup2: [number, string, boolean] = [1, "2", true];
// tup2 = ["2", 1, true]; 순서가 다르거나,
// tup2 = [1]; 길이가 다르면 안된다.

주의사항2

tup1.push(1) 혹은 tup1.pop()과 같이 배열 메소드를 사용할 때는 튜플의 길이 제한이 발동하지 않는다. 따라서, 튜플 타입을 사용할 때는 push, pop을 주의해서 사용해야 한다.

언제 튜플을 유용하게 쓰일까?

const users:[string, number] = [["제노", 1], ["이아무개", 2], [3, "최아무개"]];
와 같이 인덱스에 따라 넣어야 할 값이 정해져 있고, 순서를 지키는게 중요할 때, 튜플을 사용하면 된다.

객체

객체 사용 방법

// object: 객체 리터럴 타입
let user: {
  id: number;
  name: string;
} = {
  id: 1,
  name: "제노",
};

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

사용하지 않아도 되는 property 정의 방법(optional property)

let user: {
  id?: number;
  name: string;
} = {
  id: 1,
  name: "제노",
};

⇒ id?: id가 있어도, 없어도 된다는 의미

값이 수정되면 안되는 경우(readonly)

let config: {
  readonly apiKey: string;
} = {
  apiKey: "MY API KEY",
};

config.apiKey = "hacked";

타입 별칭(type alias)과 인덱스 시그니처

타입 별칭

let user: {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
} = {
  id: 1,
  name: "제노",
  nickname: "zenoya",
  birth: "1997.01.04",
  bio: "안녕하세요",
  location: "서울시",
};
let user2: {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
} = {
  id: 2,
  name: "제노",
  nickname: "zenoya",
  birth: "1997.01.04",
  bio: "안녕하세요",
  location: "서울시",
};

만약 위와 같이 유저가 늘어날 때 마다 유저 타입을 타이핑을 해줘야 한다면 데이터가 많아지면 굉장히 비효율적이고 중복되는 코드가 많아진다.

⇒ 이를 해결하기 위해서 타입 별칭을 사용한다.

type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
}

타입 별칭 적용

type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
};

let user: User = {
  id: 1,
  name: "제노",
  nickname: "zenoya",
  birth: "1997.01.04",
  bio: "안녕하세요",
  location: "서울시",
};
let user2: User = {
  id: 2,
  name: "제노",
  nickname: "zenoya",
  birth: "1997.01.04",
  bio: "안녕하세요",
  location: "서울시",
};

주의 사항

  1. 같은 scope 내에선 중복된 타입 별칭 명을 사용하면 안된다.

인덱스 시그니처

// 인덱스 시그니처
type CountryCodes = {
  Korea: string;
  UnitedState: string;
  UnitedKingdom: string;
  .
  .
};
let countryCodes:CountryCodes = {
  Korea: "ko",
  UnitedState: "us",
  UnitedKingdom: "uk",
  .
  .
};

만약 200개의 국가를 적어줘야 한다면 200번의 타이핑을 거쳐야 하므로, 상당히 비효율적이다. 이처럼 key와 value의 규칙을 기준으로 객체의 타입을 정의할 수 있는 문법을 인덱스 시그니처라고 한다.

문법

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

ex)

type CountryNumbercondes = {
  [key: string]: number;
};
let countryNumbercondes = {
  Korea: 410,
  UnitedState: 840,
  UnitedKingdom: 826,
};

주의사항

  1. 반드시 가져야 할 값이 있을 때
type CountryNumbercondes = {
  [key: string]: number;
  Korea: number;
};
let countryNumbercondes: CountryNumbercondes = {};

위처럼 Korea:number를 명시해주면 된다.

Enum 타입

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

const user1 = {
  name: "제노",
  role: 0, // 0 <- 관리자다
};
const user2 = {
  name: "홍길동",
  role: 1, // 1 <- 일반 유저
};
const user3 = {
  name: "아무개",
  role: 2, // 2 <- 게스트
};

위처럼 role에 대해 숫자만 보고 기억하기 어려운 상황 등이 생기는데, 이런 경우에 enum을 사용해서 해결할 수 있다.

사용 방법

enum Role {
  ADMIN,
  USER,
  GUEST,
}

const user1 = {
  name: "제노",
  role: Role.ADMIN,
};
const user2 = {
  name: "홍길동",
  role: Role.USER,
};
const user3 = {
  name: "아무개",
  role: Role.GUEST,
};

각각 0, 1, 2번으로 자동으로 할당 된다.

값을 지정해줄 수도 있다.

ex1)

enum Role {
  ADMIN = 10,
  USER,
  GUEST,
}

각각 10, 11, 12로 할당 된다.

ex2)

enum Role {
  ADMIN,
  USER = 10,
  GUEST,
}

중간에 할당을 하면 처음은 0, 그리고 10, 11 순으로 할당이 된다.

텍스트 enum 사용

enum Role {
  ADMIN = 10,
  USER,
  GUEST,
}
enum Language {
  korean = "ko",
  english = "en",
}

const user1 = {
  name: "제노",
  role: Role.ADMIN,
};
const user2 = {
  name: "홍길동",
  role: Role.USER,
  Language: Language.korean,
};
const user3 = {
  name: "아무개",
  role: Role.GUEST,
  Language: Language.english,
};
console.log(Role.ADMIN, Role.USER, Role.GUEST);

주의 사항

  1. ts는 컴파일 과정에서 타입들이 다 사라지지만, enum은 사라지지 않는다.

ch05.js

// enum 타입(열거형 타입)
// 여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입
var Role;
(function (Role) {
    Role[Role["ADMIN"] = 10] = "ADMIN";
    Role[Role["USER"] = 11] = "USER";
    Role[Role["GUEST"] = 12] = "GUEST";
})(Role || (Role = {}));
var Language;
(function (Language) {
    Language["korean"] = "ko";
    Language["english"] = "en";
})(Language || (Language = {}));
const user1 = {
    name: "제노",
    role: Role.ADMIN,
};
const user2 = {
    name: "홍길동",
    role: Role.USER,
    Language: Language.korean,
};
const user3 = {
    name: "아무개",
    role: Role.GUEST,
    Language: Language.english,
};
console.log(Role.ADMIN, Role.USER, Role.GUEST);
export {};

enum은 js 객체로 변하고 있다. ts의 enum은 컴파일 결과 js 객체로 변환되기 때문에, 값을 사용하듯이 사용할 수 있다.

Any와 Unknown 타입

Any타입

let anyVar = 10;
anyVar = "asd";

위와 같이 anyVar에서는 초기화 값으로 타입이 추론이 되어 number 타입으로 추론이 된다.

여기서 string 값을 넣게 되면 오류가 발생한다.

이때, 어떠한 타입이든지 상관없이 사용하고 싶을 때 사용하는 것이 any다.

ex)

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

any타입은 타입 검사를 안하는 것과 똑같다.

즉, ts가 가지는 이점을 포기한다는 것과 마찬가지다.

가능하면 최대한 사용하지 않는 편이 좋다.

Unknown타입

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

any와 똑같다.

어떤 타입의 변수가 들어올 지 모르겠다 할 때 any 혹은 unknown을 사용할 수 있다.

차이점

  1. unknown은 모든 값을 저장할 순 있지만, 반대로는 안된다.
let num: number = 10;
num = unknownVar; // 집어넣을 수 없다.
  1. unknown은 toUpperCase()와 같은 함수, 연산자체를 사용할 수 없다.

Void와 Never 타입

void 타입

function func1(): string {
  return "hello";
}

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

어떤 함수에서 return 값이 있을 때는 해당 타입을 명시해주고, func2()와 같이 없다면 void로 해주면 된다.

void를 사용하면

let a: void;
a = 1; // x
a = "hello"; // x
a = {}; // x
a = undefined // o

어떤 변수를 void 타입으로 지정하면, 아래 대입 연산에서 오류가 발생한다. (undefined는 가능)

never타입

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

애초에 반환을 할 수 없어서, 애초에 정상적으로 종료가 되지 않을 때, 반환을 한다는 것이 애초에 모순이 있을 때, never 타입을 써준다.

let a: never;

변수를 never로 선언하면, void와 달리 어떠한 값도 넣지 못한다.

섹션4. 타입스크립트 이해하기

타입스크립트 이해하기

타입스크립트를 이해한다는 것은?

  1. 어떤 기준으로 타입을 정의하는지
  2. 어떤 기준으로 타입간의 관계를 정의하는지
  3. 어떤 기준으로 타입의 오류를 검사하는지

⇒ 이런 타입스크립트의 구체적인 원리와 동작 방식을 살펴보는 것

타입은 집합이다.

집합이란

동일한 속성을 갖는 여러개의 원소, 요소들을 묶어놓은 단위

여러가지의 숫자를 모아놓은 집합을 ts에서는 number type이라고 이름을 지어서 사용한 것이다.

let num: 20 = 20;

20이라는 하나의 값만 가지는 number literal type이다.

이 타입 또한 number type이자, 부분 집합이다.

타입들간의 부모-자식관계를 계층적으로 표현하면 다음과 같다.

타입 호환성

어떤 타입을 다른 타입으로 취급해도 괜찮은지 판단하는 것

number 타입이 number 리터럴 타입보다 더 큰 집합이기 때문에, number 타입을 number 리터럴 타입으로 취급할 수 없다.

코드 예시

let num1: number = 10;
let num2: 10 = 10;

num1 = num2 // o

num2는 리터럴 타입, num1는 number 타입이며 num1에 포함되는 값이기 때문에 가능하다.

let num1: number = 10;
let num2: 10 = 10;

num2 = num1; // 오류 발생

num1에 있는 값이 10이라 만족하긴 하지만, 나중에 40, 50 등 다른 값을 넣을 수도 있기 때문에 10이라는 리터럴 타입에 속한다고 볼 수 없기 때문에 불가능 하다.

업 캐스팅과 다운 캐스팅

업 캐스팅

작은 집합을 큰 집합에 속하게 하는 것

다운 캐스팅

큰 집합을 작은 집하게 속하게 하는 것

업 캐스팅은 모든 상황에 가능하고 다운 캐스팅은 대부분의 상황에서 불가능 하다.

타입 계층도와 함께 기본타입 살펴보기

타입 계층도

unknown 타입

// unknown 타입
function unknownExam() {
  // 업캐스팅
  let a: unknown = 1;
  let b: unknown = "hello";
  let c: unknown = true;
  let d: unknown = null;
  let e: unknown = undefined;

  let unknownVar: unknown;

  // 다운 캐스팅
  let num: number = unknownVar;
  let str: string = unknownVar;
  let bool: boolean = unknownVar;
}

unknown은 슈퍼타입이므로 모든 값을 넣을 수 있다. 반대로는(다운 캐스팅) 불가능하다.

never 타입

// never 타입
// 가장 아래에 위치
// 모든 타입의 서브타입, 즉 모든 집합의 부분 집합이라는 의미 => 공집합(아무것도 없는 집합)

function neverExam() {
  function neverFunc(): never {
    while (true) {}
  }
}

즉, 이 함수가 반환하는 값의 종류는 공집합이다 라는것이고 반환할 수 있는 것이 아무것도 없다는 의미이다.

function neverExam() {
  function neverFunc(): never {
    while (true) {}
  }
  // 업 캐스팅
  let num: number = neverFunc();
  let str: string = neverFunc();
  let bool: boolean = neverFunc();
}

반대로는 모든 집합의 부분집합 이므로 업 캐스팅이며, 모든 값들에 never 타입의 값을 넣을 수 있다.

function neverExam() {
  function neverFunc(): never {
    while (true) {}
  }
  // 업 캐스팅
  let num: number = neverFunc();
  let str: string = neverFunc();
  let bool: boolean = neverFunc();
  // 다운 캐스팅
  let never1: never = 10;
  let never2: never = "string";
  let never3: never = true;
}

반대로는 큰 집합이 작은 집합으로 들어가는 것이므로 다운 캐스팅이다. 따라서 오류가 발생한다.

void 타입

// void 타입
function voidExam() {
  function voidFunc(): void {
    console.log("hi");
    return undefined;
  }
  // 업캐스팅
  let voidVar: void = undefined;
}

⇒ void는 undefined의 슈퍼타입 이므로 void 타입에 undefined를 넣을 수 있고, 리턴 또한 가능하다(업캐스팅)

any 타입

function anyExam() {
  let unknownVar: unknown;
  let anyVar: any;
  let undefinedVar: undefined;

  anyVar = unknownVar;
  undefinedVar = anyVar;
}

unknown의 서브 타입이지만, 치트키 타입이므로 타입 계층도를 완벽하게 무시한다.
즉, 모든 타입의 슈퍼타입으로 위치하기도 하고, 모든 타입의 서브 타입으로도 위치하기도 한다.

예외

function anyExam() {
  let neverVar: never;

  neverVar = anyVar;
}

never 타입은 순수한 공집합이기 때문에, 어떠한 타입도 다운 캐스팅을 할 수 없다.

0개의 댓글