타입 정의, 타입 별칭

📘 쉽게 시작하는 타입스크립트 스터디

🚀 연산자를 사용한 타입 정의

📌 유니언 타입

유니언 타입(union type)은 여러개의 타입 중 한개만 쓰고 싶을 때 사용하는 문법이다.
자바스크립트의 OR 연산자의 |를 이용하여 여러개의 타입 중 1개를 사용하겠다고 선언하는 방식이 바로 유니언 타입니다.

function logText(text: string | number) {
	console.log(text);
}
logText('hi');
logText(100);

📌 유니언 타입의 장점

유니언 타입은 다음과 같이 string과 number에서 모두 제공하는 toString()을 자동 완성할 수 있지만 any 타입은 자동 완성 되지 않는다.

function logText(text: string | number) {
	console.log(text.toString());  //hi
}

logText('hi');

📌 유니언 타입을 사용할 때 주의할 점

타입스크립트 입장에서는 함수에 인자를 넘겨 실행할 때 Person 타입이 올지 Developer 타입이 올지 알 수 없기 때문에 어느 타입이 오더라도 문제 없을 공통 속성인 name 속성만 자동 완성해준다.

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

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

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

introduce({ name: 'captin', skill: 'lecture' });

여기서 함수 내부에서 파라미터 타입의 종류에 따라 특정 로직을 실행하고 싶다면 다음과 같이 in 연산자를 사용해서 로직을 작성하면 된다.

function logText(text: string | number) {
	if (typeof text === 'string') {
		console.log(text.toLowerCase);
	}
	if (typeof text === 'number') {
		console.log(text.toLocaleString);
	}
}

이처럼 함수의 파라미터에 유니언 타입을 선언하면 함수 안에서는 두 타입의 공통 속성과 메서드만 자동 완성 된다.
특정 타입의 속성과 메서드를 사용하고 싶다면 typeof나 in 연산자를 사용하여 타입을 구분한 후 코드를 작성한다.
이런 동작은 타입 가드라고 한다.

📌 인터섹션 타입

인터섹션 타입(intersection type)은 타입 2개를 하나로 합쳐서 사용할 수 있는 타입이다.

interface Avenger {
	name: string;
}
interface Hero {
	skill: string;
}
function introduce(someone: Avenger & Hero) {
	console.log(someone.name);
	console.log(someone.skill);
}
introduce({
	name: 'captain', skill: 'power'
});

이 코드는 name 속성 갖는 Avenger 인터페이스와 skill 속성을 갖는 Hero 인터페이스를 선언하고 introduce() 함수의 파라미터에 인터섹션 타입(&)으로 정의했다.
Avenger 타입과 Hero 타입을 인터섹션 타입으로 정의 했기 때문에 someone 파라미터에는 두 타입의 name과 skill 속성을 모두 사용할 수 있다. name과 skill 속성 중 하나라도 누락하여 객체를 넘긴다면 에러가 발생한다.


🚀 타입 별칭

📌 타입 별칭이란?

타입 별칭(type alias)은 특정 타입이나 인터페이스 등을 참조할 수 있는 타입 변수를 의미한다.
타입에 의미를 부여해서 별도의 이름으로 부르느 것이다. 변수처럼 해당 타입이 어떤 역할을 하는지 이름을 짓고 싶을 때 사용할 수 있고, 여러번 반복되는 타입을 변수화해서 쉽게 표기하고 싶을 때도 사용한다.

이 코드는 MyName이라는 타입 별칭을 선언하고 string 타입을 할당한다.

type MyName = string;
var capt: MyName = '캡틴';

이 코드는 캡틴이라는 문자열을 capt 변수에 할당한다.
var capt: string = '캡틴'; 과 역할이 같지만 사용할 타입이 구체적으로 어떤 의미를 지니는지 알 수 있다.
타입 별칭을 썼을 때 가장 큰 장점은 반복되는 타입 코드를 줄여 줄 수 있다는 것이다.

function logtext(text: string | number) {
}
var message: string | number = "안녕하세요";
logText(message);

string | number 타입이 반복되는 것을 볼 수 있다. 앞으로 이 함수를 호출하기 위해 더 많은 변수가 필요할 수 있는데 타입 별칭으로 줄일 수 있다.

type MyMessage = string | number;
function logtext(text: MyMessage) {
}
var message: MyMessage = "안녕하세요";
logText(message);

이렇게 타입 별칭을 사용하면 타입에 의미를 담아 여러 곳에 재사용 할 수 있다.

📌 타입 별칭과 인터페이스의 차이점

👉 코드 에디터에서 표기 방식 차이

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

var seho: User;
// User의 타입별칭에 마우스 커서를 올리면 다음과 같이 타입 정보가 표시된다.
// type User = {  
// id: string;  
// name: string;  
// }

interface Admin {
	id: string;
	name: string;
}

var yurim: Admin;
// Admin 타입에 마우스 커서를 올리면 다음과 같이 타입 정보가 표시된다.
// interface Admin 

변수에 연결된 타입이 구체적으로 어떤 모양인지 파악할 때는 타입 별칭이 더 좋아 보인다. 그럼 타입 별칭만 쓰는 것이 더 좋을까?

👉 사용할 수 있는 타입의 차이

인터페이스는 주로 객체 타입을 정의하는데 사용하는 반면, 타입 별칭은 다음과 같이 일반 타입에 이름을 짓는데 사용하거나 유니언, 인터섹션 타입 등에 사용한다.

type ID = string;
type Product = TShirt | Shoes;
type Teacher = Person | Adult;

반대로 이런 타입들은 인터페이스로 정의할 수 없다. 그리고 타입 별칭은 뒤에서 배울 제네릭이나 유틸리티 타입 등 다양한 타입에 사용할 수 있다.

type Gilbut<T> = {
	book: T;
}

type MyBeer = Pick<Beer, 'brand'>;

혹은 다음과 같이 인터페이스와 타입 별칭의 정의를 함께 사용할 수도 있다.

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

type Adult = {
	old: boolean;
}

type Teacher = Person & Audlt;

👉 타입 확장 관점에서 차이

타입 확장이란 이미 정의되어 있는 타입들을 조합해서 더 큰 의미의 타입을 생성하는 것이다.

  • 인터페이스 타입을 확장하는 방법 :
interface Person {
	name: string;
	age: number;
}

interface Developer expends Person {
	skill: string;
}

var joo: Developer = {
	name: '형주',
	age: 21,
	skill: '웹개발'
}

인터페이스는 타입을 확장할 때 상속이라는 개념을 이용한다. 앞서 배웠듯이 extends라는 키워드를 사용해서 부모 인터페이스의 타입을 자식 인터페이스에 상속해서 사용할 수 있다.
반면 타입 별칭은 인터섹션 타입으로 객체 타입을 2개 합쳐서 사용할 수 있다.

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

type Developer {
	skill: string;
}

type Joo = Person & Developer;

var joo: Joo = {
	name: '형주',
	age: 21,
	skill: '웹개발'
}

Person & Developer 타입을 Joo라는 타입 별칭으로 정의하여 joo 변수에 연결했다.
작성된 타입을 어떻게 조합하느냐에 따라 인터페이스를 쓰기도하고 때로는 타입 별칭을 사용 할 수도 있다.
이때 추가로 알아 두면 좋은 인터페이스의 선언 병합(declaration merging) 이라는 성질이 있다.
동일한 이름으로 인터페이스를 여러번 선언했을 때 해당 인터페이스의 타입 내용을 합치는 것을 선업 병합이라고 한다.

📌 타입 별칭은 언제 쓰는 것이 좋을까?

타입 별칭으로만 타입 정의가 가능한 곳에서는 타입 별칭을 사용하고 백엔드와의 인터페이스를 정의하는 곳에서는 인터페이스를 이용하자.

👉 타입 별칭으로만 정의할 수 있는 타입들

타입 별칭으로만 정의할 수 있는 타입은 주요 데이터 타입이나 인터섹션, 유니언 타입이다.
인터페이스는 객체 타입을 정의할 때 사용하는 타입이기 때문에 다음 타입은 인터페이스로 정의할 수 없다.

type ID = string;
type Product = TShirt | Shoes;
type Teacher = Person | Adult;

그리고 타입 별칭은 뒤에서 배울 제네릭(generic), 유틸리티 타입(utility type), 맵드 타입(mapped type)과도 연동하여 사용할 수 있다.

// 제네릭
type Dropdown<T> = {
	id: string;
	title: T;
}

// 유틸리티 타입
type Admin = { name: string; age: number; role: string; }
type OnlyName = Pick<Admin, 'name'>

// 맵드 타입
type Picker<T, extends keyof T> = {
	[P in K]: T[P];
}

제네릭은 인터페이스와 타입 별칭에 모두 사용할 수 있지만 유틸리티 타입이나 맵드 타입은 타입 별칭으로만 정의할 수 있다. 유틸리티 타입이나 맵드 타입은 기존에 정의된 타입을 변경하거나 일부만 활용할 때 사용한다.

👉 백엔드와의 인터페이스 정의

웹 서비스를 프런트엔드와 백엔드로 역할을 나누어 개발할 때 백엔드에서 프런트엔드로 어떻게 데이터를 넘길지 정의하는 작업이 필요하다. 이때 이 작업을 인터페이스를 정의한다고 흔히 이야기한다.
보통 이 데이터를 정의하면서 프런트엔드에서는 API 함수를 설계한다.
서버에 데이터를 요청하고 받은 결과를 화면에서 처리해 줄 수 있도록 API 함수를 제작한다.

interface Admin {
	role: string;
	department: string;
}

// 상속을 통한 인터페이스 확장
interface User extends Admin {
	id: string;
	name: string;
}

// 선언 병합을 통한 타입 확장
interface User {
	skill: string;
}

서비스 요구사항이 변경되어 화면에 노출해야 하는 데이터 구조가 바뀌었을때, 사용자 객체의 속성에 role, address 등이 추가되거나 다른 객체 정보와 결합하여 표시되어야 한다면 기존 타입의 확장이라는 측면에서 인터페이스로 정의하는 것이 더 수월하다.
이 코드는 API 함수의 반환 타입 User를 인터페이스의 특징들로 확장한다.
인터페이스는 마치 다음과 같이 정의한 것처럼 동작한다.
이처럼 유연하게 타입을 확장하는 관점에서는 타입 별칭보다 인터페이스가 더 유리하다.

	id: string;
	name: string;
	role: string;
	department: string;
	skill: string;