[TypeScript] Section 3. 유니언, 인터섹션, 이넘

서진·2023년 11월 14일

TypeScript

목록 보기
3/4
post-thumbnail

📍 유니언 타입

유니언 타입 | 은 타입을 지정할 때 여러가지 타입을 연결하여 지정해둘 수 있는 방식으로 자바스크립트의 OR 연산자 ||와 같다 !

📝 코드 예시

function printTest(text: string | number) {
   if (typeof text === 'string') {
     text.includes('a') // 문자 타입과 관련된 api 자동완성됨
   }
   if (typeof text === 'number) {
     text.toFixed(); // 숫자 타입과 관련된 api 자동완성됨
  	 console.log(text)
   }
}
  1. printTest함수는 파라미터로 문자열 혹은 숫자 타입을 전달받을 수 있다.
  2. printTest함수 내부에서는 인자로 받아오는 값의 type에 따라 다른 동작을 처리할 수 있다.
  3. 전달받는 type에 따라 코드를 작성할 경우, 해당 type에서 사용할 수 있는 API를 자동완성으로 알려주어 쉽게 사용할 수 있다.

📍 인터섹션 타입

& 연산자를 이용해 여러 개의 타입 정의를 하나로 합치는 방식

📝 코드 예시

interface Animal {
  name: string;
  age: number;
}
  
interface Cat {
  type: string;
  age: number;
}
  
type pet = Animal & Cat;
  1. pet 타입에는 AnimalCat타입들이 합쳐져서 할당된다.
  2. 결과적으로 pet은 아래와 같이 정의된다
{
  name: string;
  age: number;
  type: string;
}

🚨 유니언 타입 사용시 주의할 점

유니언 타입을 언어 그대로의 OR로 생각해서 인터페이스와 같은 타입을 다룰 때 오류를 범할 수 있다!

또는이라고 한다면 A인터페이스와 B인터페이스를 |를 통해 지정된 새로운 타입에서는 각 인터페이스가 가지는 서로 다른 속성들을 모두 사용할 수 있을 것 같지만 ... 실상은 그렇지 않다.

오히려! 둘 중 어느 타입이 올지 모르니 두 인터페이스가 공통으로 가지고 있는 속성에만 접근할 수 있다.

📝 코드 예시

interface Animal {
  name: string;
  age: number;
}
  
interface Cat {
  type: string;
  age: number;
}
  
function myPet(pet: Animal | Cat) {
  console.log(pet.age); // ⭕️
  console.log(pet.type); // ❌ Cat에만 있는 속성이여서 타입 에러
  console.log(pet.name); // ❌ Animal에만 있는 속성이여서 타입 에러
}

🤔 타입 가드

만약 유니언을 사용하면서 모든 속성에 접근하고 싶다면? 타입 가드를 통해 접근하여 사용할 수 있다.

타입 가드란?

데이터의 타입을 알 수 없거나, 될 수 있는 타입이 여러 개라고 가정할 때 조건문을 통해 데이터의 타입을 좁혀나가는 것

✅ 타입가드를 사용한 구별된 유니언

타입을 구별할 수 있는 단서가 있는 유니언 타입

📝 코드 예시

type Animal {
  name: string;
  age: number;
}
  
type Cat {
  type: string;
  age: number;
}
  
function myPet(pet: Animal | Cat) {
  if('name' in pet) {
  	console.log(pet.name); 
  } else {
    console.log(pet.type);
  }
}
  1. 각 타입에 타입을 구별할 단서를 넣는다.
  2. 조건문을 통해 각 타입의 단서로 어떤 타입인지 추론한다.
    -> nameAnimal에만 있으니 Animal 타입으로 추론 가능하다.
  3. 각 타입에 맞는 속성에 접근하여 사용 가능!

💡 +) type aliases VS interface

아티클을 작성하면서 문득 .. type aliasesinterface의 차이가 무엇인지 궁금해졌고 .. 잠깐 주제를 벗어나 정리하고 가려고 합니다 ,,

🍀 type

  • data의 형태를 정의할 수 있는 방법
  • string, boolean, number, array, tuple, enum, advanced types 가진다.

🍀 type aliases

  • 이미 존재하는 타입에 새로운 이름을 지정할 수 있는 방법 (새로운 타입을 정의하는 것은 아님)
  • type 키워드를 통해서 사용할 수 있고 typescript 내에 존재하는 type이라면 모두 새로운 이름을 붙여줄 수 있다.
type MyNumber = number; // number 타입을 MyNumber로 대신해서 사용 가능

// user가 가지는 타입들을 미리 정의해서 User로 대신 사용 가능
type User = {
  id: number;
  name: string;
  email: string;
}
  • 동일한 타입 필드를 가지더라도 여러개의 이름으로 사용할 수 있다.
type ErrorCode = string | number;
type Answer = string | number;

// 의도에 따라 type alias를 지정하여 코드를 더 읽기 쉽게 만들 수 있다!

🍀 interfaces

객체가 지켜야 하는 속성들을 정의해두는 것

interface Client { 
    name: string; 
    address: string;
}

하지만 위의 예시를 type alias로 동일하게 작성 가능하다

type Client = {
  name: string;
  address: string;
}

이와 같은 경우에서는 typeinterfaces 중 어느 것을 사용해도 무방하지만 그래도 type을 쓰는 경우와 Interface를 쓰는 경우를 나누어볼 수 있다!

🧐 types VS interfaces

1. primitive alias - type

prmitive typealias를 적용하고 싶은 경우에는 type을 사용해야 한다. interface는 오직 객체에만 사용할 수 있다!

2. unioin type - type

union type은 오직 type을 통해서만 정의될 수 있다.
interface 키워드를 사용하여 직접적으로 Union Type을 정의하는 것은 불가능하다.
(단, 2개의 interfaces를 사용해서 새로운 union type을 만드는 것은 가능하다.)

// ⭕️
type Transport = 'Bus' | 'Car' | 'Bike' | 'Walk';

// ❌ interface 키워드를 사용하여 직접적으로 Union Type을 정의
interface Shape {
  kind: "square";
  size: number;
} | {
  kind: "circle";
  radius: number;
};

// ⭕️
interface CarBattery {
  power: number;
}
interface Engine {
  type: string;
}
type HybridCar = Engine | CarBattery;

3. 확장 - interface / intersection(&) - type

1️⃣ 인터페이스는 여러개의 인터페이스extends 키워드를 통해 확장할 수 있다.

interface VIPClient extends Client {
    benefits: string[]
}
  • 인터페이스가 아닌 type 타입도 확장이 가능하다.
type Client = {
    name: string;
};

interface VIPClient extends Client {
    benefits: string[]
}

cf) 다만 유니언 타입은 어느 것이 확장될지 모르기 때문에 확장 불가능하다.

  • 반면 typeintersection &을 사용하여야한다.
// 여기서 Client는 type alias를 통해 정의되었다고 가정
type VIPClient = Client & {benefits: string[]}; 

2️⃣ 확장을 통해 새롭게 정의된 인터페이스는 기존 인터페이스의 속성과 메소드들에 모두 접근 가능하고 새롭게 추가할 수 있다.

이때 interface는 동일한 key값이 있는 경우 에러를 발생시킨다.

interface Person {
  getPermission: () => string;
}

// Interface 'Staff' incorrectly extends interface 'Person'
interface Staff extends Person {
   getPermission: () => string[]; // 동일한 이름 에러 발생
}

반면 type aliases에서는 동일한 key가 있어도 에러를 발생시키지 않고 자동으로 합쳐준다. 때문에 예상하지 못한 에러 발생에 주의해야 한다!

type Person = {
  getPermission: (id: string) => string;
};

type Staff = Person & {
   getPermission: (id: string[]) => string[];
};

const AdminStaff: Staff = {
  getPermission: (id: string | string[]) =>{
    return (typeof id === 'string'?  'admin' : ['admin']) as string[] & string;
  }
}

4. 여러 type 기능 사용 목적 - type

mapped types, conditional types, type guards 등은 type을 통해서만 사용 가능하다. interface로는 사용할 수 없다!

Types vs. interfaces in TypeScript


📍 Enum

특정 값들의 집합을 의미하는 자료형으로 숫자형과 문자형이 있다.
enum 이름은 타입으로만 지정할 수 있다.
enum 데이터는 타입으로 사용할 수 있고, 값으로도 사용할 수 있다.

✅ 숫자형 이넘

  1. 초기 값 안 주고 사용하면 0부터 시작해서 1씩 증가한다
enum Direction {
  Up, // 0
  Down, // 1
  Left, // 2
  Right // 3
}
  1. 초기값을 설정하면 초기값부터 차례대로 1씩 증가한다
enum Direction {
  Up = 3 // 3
  Down, // 4
  Left, // 5
  Right // 6
}

✅ 문자형 이넘

문자형 이넘은 숫자형 이넘과 거의 비슷하지만 이넘 값 전부 다 특정 문자 혹은 다른 이넘 값으로 초기화해주고 사용해야 한다.

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

let left = Direction.Left; // 'LEFT'
profile
🫧 ☁️ 🌙 👩🏻•💻 🌿 🐱 🖱 🍟 🚀 ⭐️ 🧸 🍀 💗

4개의 댓글

comment-user-thumbnail
2023년 11월 14일

잘 봤습니다. 좋은 글 감사합니다.

답글 달기
comment-user-thumbnail
2023년 11월 15일

서진 언니 아티클을 보면서 type aliases와 interface 차이가 더 궁금해져서 찾아보니까 합 타입 혹은 튜플 타입을 써야 되는 상황이 아니라면 interface가 선언병합이 가능해서 interface를 더욱 권장한다고 하네요! 깔끔하게 정리해준 덕분에 훨씬 이해가 잘 됐어요! 최고입니당👍제가 읽은 글도 아래 첨부해둬요!
https://medium.com/humanscape-tech/type-vs-interface-언제-어떻게-f36499b0de50

답글 달기
comment-user-thumbnail
2023년 11월 15일

누가 이렇게 깔끔하게 정리를.... types 랑 interfaces 비교까지 야무지게 해줘서 복습 알차게 하고가용

답글 달기
comment-user-thumbnail
2023년 11월 15일

각 타입 사용 시 주의할 점을 적어줘서 이해가 빨랐던 거 같습니다 :) 훌륭한 아티클 감사합니다 🔥

답글 달기