타입 정의, 타입 별칭
유니언 타입(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;