본문 바로가기

Frontend Dev/TypeScript

타입스크립트 유니온/교차 타입과 리터럴 타입

반응형

 TypeScript의 타입은 연산자를 이용해 조합해서 사용할 수 있다. 다소 복잡한 타입을 표현하고 싶을 때, 지정한 여러 타입의 합집합을 의미하는 Union 타입과 교집합을 의미하는 Intersection 타입을 사용할 수 있다.

 Union 타입은 | 연산자를, Intersection 타입은 & 연산자를 사용한다.

 

👾 Union Types

유니온 타입은 여러 타입 중 하나가 될 수 있는 값으로, 서로 다른 두 개 이상의 타입을 세로 막대 | 로 구분하여 만든다.

let value: string | number | boolean;

 

👩🏻‍💻 Union type 기본 예시

function printValue(value: number | string): void {
  if (typeof value === 'number') {
    // 이 분기에서 value는 'number' 타입을 가진다.
    console.log(`The value is a number: ${value}`);
  } else {
    // 이 분기에서 value는 'string' 타입을 가진다.
    console.log(`The value is a string: ${value}`);
  }
}

printValue(10); // The value is a number: 10
printValue('hello'); // The value is a string: hello

💬 유니온 타입은 다양한 타입의 값을 처리해야 하는 경우 유용하다.

❗️ 만약 value: any로 타입을 정의한다면 JavaScript로 작성하는 것과 큰 차이가 없다.

 

✔️ Union 타입 사용시 장점

 Union 타입을 사용하면 타입을 추론할 수 있기 때문에, 타입에 관련된 API를 쉽게 자동완성으로 얻어낼 수 있다.

 

 

✔️ Union 타입 사용시 유의점

유니온 타입인 값이 있으면, 유니온 있는 모든 타입에 공통인 멤버들에만 접근할 수 있다.

 

interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

function askSomeone(someone: Developer | Person) {
  console.log(someone.name);
}

💬 askSomenone 함수 내부에서는 Developer와 Person이 갖고 있는 공통 프로퍼티인 name에만 접근할 수 있다.

만약 나머지 프로퍼티에도 접근하고 싶다면 타입 가드를 사용해야 한다.

 

 

✔️ 타입 가드(Type Guard) 사용하기

 Type Guard란 TypeScript에서 타입을 보호하기 위해 사용되는 기능 중 하나이다. 타입 가드는 특정 코드 블록에서 타입의 범위를 제한해 해당 코드 블록 안에서 타입 안정성을 보장해 준다.

 

interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

function askSomeone(someone: Developer | Person) {
  if ('skill' in someone) {
    console.log(someone.skill);
  }

  if ('age' in someone) {
    console.log(someone.age);
  }
}

askSomeone({ name: 'Hana', skill: 'Javascript' });
askSomeone({ name: 'Jay', age: 20 });

💬 in 연산자를 사용하여 해당 프로퍼티가 객체 내에 존재하는지 여부를 검사할 수 있다.

💬 유니온 타입은 공통되지 않은 프로퍼티(skill age)에 접근하기 위해 타입 가드를 해줘야 하지만 전달인자를 전달할 때 선택지가 생기게 된다.

 

 

✔️ 식별 가능한 유니언 타입

interface Car {
  name: "car";
  color: string;
  start(): void;
}

interface Mobile {
  name: "mobile";
  color: string;
  call(): void;
}

function getGift(gift: Car | Mobile) {
  console.log(gift.color);

  // 검사해야할 항목이 많아지면 switch를 사용하는게 가독성이 좋음.
  if (gift.name === "car") { 
    gift.start();
  } else {
    gift.call();
  }
}

getGift({ name:"car", color: "blue", start() { console.log("🚙 car") } }) 
// "blue", "🚙 car" 출력
getGift({ name:"mobile", color: "silver", call() { console.log("📞 call") } }) 
// "silver", "📞 call" 출력

( 👀 식별 가능한 유니언 타입, 이 부분은 사실 아래와 같이 이해하는게 맞는지 모르겠다. 혹시 아시는 분 있으시다면 댓글 부탁드립니다. 🙏🏻 )

💬 식별 가능한 유니언 타입 : 동일한 이름의 속성을 정의하고 (name), 그것의 타입을 다르게 줌으로써 (Car또는 Mobile) 두 개의 인터페이스를 구분할 수 있다. 위 예제에서 Car는 “car”타입, Mobile은 “mobile” 타입이 된다.

 

 

👾 Intersection Types

 인터섹션 타입은 둘 이상의 타입을 결합하여 새로운 타입을 만드는 방법으로 & 로 표기한다. 기존 타입을 합쳐 필요한 기능을 모두 가진 단일 타입을 얻을 수 있다.

 

👩🏻‍💻 Intersection type 예시 코드

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}

type ColorfulCircle = Colorful & Circle;

function draw(circle: ColorfulCircle) {
  console.log(`Color: ${circle.color}, Radius: ${circle.radius}`)
}

draw({ color: "yellow", radius: 20 }) // "Color: yellow, Radius: 20"
interface Car {
  name: string;
  start(): void;
}

interface Toy {
  name: string;
  color: string;
  price: number;
}

const toyCar: Toy & Car = {
  name: "타요",
  color: "blue",
  price: 5000,
  start() {},
}

console.log(toyCar) // { "name": "타요", "color": "blue", "price": 5000 }
interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

type User = Developer & Person;
// User 변수는 Developer, Person 각각에 정의된 속성 모두를 받는다.

function askSomeone(someone: User) { 
  // askSomeone 함수 내에선 User 타입에 정의된 모든 프로퍼티에 접근할 수 있다.
  console.log(someone.name); 
  console.log(someone.skill);
  console.log(someone.age);
}

askSomeone({ name: "Elle", skill: "TypeScript", age: 27 });
// "Elle", "TypeScript", 27

💬 인터섹션 타입을 사용하면 타입을 연결해 하나의 단일 타입으로 표현할 수 있기 때문에, 타입 가드가 필요하지 않다.

💬 하지만 인터섹션 타입은 타입 가드는 필요없지만 전달인자를 전달할 때 모든 프로퍼티를 전부 보내주어야 한다. (위 예제에서는 name skill age 모두 전달해주어야 한다.)

 

 

👾 Literal Types

 리터럴 타입을 사용하면 정해진 문자열이나 수치만 대입할 수 있는 타입으로 제어할 수 있다. 리터럴 타입은 그 자체만으로는 그다지 유의미하지 않지만 유니온 타입과 함께 사용하면 보다 유용하게 사용할 수 있다.

// 문자열 리터럴 타입
const userName = "Harry";

// 유니온 타입과 함께 사용시 특정 종류의 값들만을 정의하는데 사용할 수 있다.
let postStatus: "draft" | "published" | "deleted"

 

👩🏻‍💻 Literal type 기본 예시

type Job = "police" | "developer" | "teacher"; // 문자열 Literal type

interface User {
  name: string;
  job: Job;
}

const user: User = {
  name: "Hanna",
  job: "teacher" 
  // job은 "police" | "developer" | "teacher" 로 제한된다.
  // 타입 선언에 없는 문자열이 할당되면 컴파일시 에러가 난다.
}
interface HighSchoolStudent {
  name: string;
  grade: 1 | 2 | 3; // 숫자 Literal type
}

const student: HighSchoolStudent = {
  name: "Joy",
  grade: 3
}

 


✏️ 공부하며 정리한 내용입니다. 잘못된 정보나 더 공유할 내용이 있으면 댓글로 알려주세요!

읽어주셔서 감사합니다 😊

반응형