🖥️

6. 객체의 타입을 정하자, 인터페이스

6.1 인터페이스란?

 
🗣
인터페이스란? 인터페이스는 타입 체크를 위해 정의한 약속 또는 규칙을 말한다. 일반적으로 인터페이스는 아래 항목에 사용하며 여러 타입으로 이루어진 새로운 타입을 정의하는 것과 같다. ES6에서는 지원하지 않지만 TypeScript에서는 지원한다.
  • 객체의 스펙(속성과 속성의 타입)
  • 함수의 파라미터
  • 함수의 스펙(파라미터와 반환타입)
  • 배열과 객체를 접근하는 방식
  • 클래스
 
interface 인터페이스이름{ 속성: 타입; }
인터페이스 생성 규칙
인터페이스의 생성은 다음과 같은 규칙을 따른다. 맨 앞에 interface 키워드를 적은 뒤 만들고 싶은 인터페이스의 이름을 작성해준다. 인터페이스의 이름은 일반 변수와 헷갈리지 않도록 하기 위해서 관행적으로 맨 앞 글자를 대문자로 작성한다. 그리고 중괄호 안에는 인터페이스 안의 여러 속성과 속성의 타입을 적어준다.
 
  • 인터페이스 적용 전
let robot = { name: 'mimi', age: 3, available: true}; function get_available(obj: { available: boolean }) { console.log(obj.available); // true } get_available(robot); // true
위 get_available 함수에서 받는 인자의 형태는 available 라는 속성을 갖는 객체이다. 이처럼 인자를 받을 때 단순한 타입 뿐만 아니라 객체의 속성 타입까지 정의할 수 있다. 여기에 인터페이스를 적용하면 어떻게 될까?
 
  • 인터페이스 적용 후
interface Robot_property { age: number, available: boolean; } let robot = { name: 'mimi', age: 3, available: true}; function get_available(obj: Robot_property) { console.log(obj.available); // true } get_available(robot); // true
get_available()의 인자가 좀 더 명시적으로 바뀐 것을 확인할 수 있다. get_available()의 인자는 Robot_property라는 새로운 타입을 가져야 한다. 인자로 받는 객체의 속성 개수와 인터페이스의 속성 개수는 일치하지 않아도 된다. 인터페이스의 속성들을 객체가 모두 가져야 하지만, 그 외의 속성도 가질 수 있다는 뜻이다. 만약, 인터페이스의 속성들을 객체가 가지고 있지 않다면 오류를 출력해준다.
 
즉, 인터페이스에 정의된 속성과 타입의 조건만 성립이 된다면 객체의 속성 개수는 더 많아도 문제가 없고, 인터페이스에 선언된 속성의 순서 또한 지키지 않아도 된다.
 
💡
인터페이스와 타입 별칭, 뭐가 다를까? 타입에 새로운 이름을 붙여준다는 점에서 비슷하지만, 몇가지 차이점이 존재한다. 그 중에서도 가장 큰 차이점은 타입의 확장 가능 여부이다.
 
더 자세한 내용은 6.3 인터페이스와 타입 별칭 비교 에서 알아보기로 한다.
 
 

6.2 인터페이스의 사용

6.2.1 변수의 타입을 인터페이스로 지정

interface Menu { name: string; price: number; onsale: boolean; }
인터페이스를 선언 방식에 따라 Menu라는 이름을 가진 인터페이스를 선언해주었다. 어떠한 변수의 타입을 Menu 인터페이스로 지정한다면, 그 변수는 반드시 키 값이 name, price, onsale인 세 가지 속성을 모두 가져야 한다. 또한 그 속성들은 각각 String, Number, Boolean 타입이어야 한다. 그렇다면 직접 변수를 선언하여 타입을 Menu 인터페이스로 지정하여 사용해보도록 하자.
 
interface Menu { name: string; price: number; onsale: boolean; } const americano: Menu = { name: '아메리카노', price: 4000, onsale: true }
americano라는 변수를 새로 생성하여 그 타입을 Menu 인터페이스로 지정해주었다. 즉, americano 변수는 name, price, onsale 키 값들을 가진 객체여야 한다는 뜻이다. americano의 속성들의 타입도 인터페이스의 규칙을 따라 정의해주었다. 위 예시에서 Menu 인터페이스를 타입으로 하는 americano 객체가 오류없이 정상적으로 생성되었다.
 
그런데 만약 Menu 인터페이스를 따르는 객체가 name, price, onsale 속성 중 하나라도 누락 되었거나 각 속성의 타입이 일치하지 않은 경우에는 에러를 출력한다. 각각의 경우를 코드를 통해 확인해보도록 하자. 먼저 새로운 객체 lemonade가 인터페이스의 속성 중 2가지(name, price)만 정의되었다고 하면 어떻게 작동하는지 확인해보도록 하자.
 
  • 인터페이스의 속성을 모두 가지고 있지 않은 경우
interface Menu { name: string; price: number; onsale: boolean; } const lemonade: Menu = { name: "레몬에이드", price: 4500 }
notion imagenotion image
누락된 ‘onsale’ 속성이 Menu 형식에 필수적이라는 에러 메세지를 확인할 수 있다. 이러한 에러는 boolean 타입의 onsale 속성을 추가해줌으로써 해결할 수 있다.
 
const lemonade: Menu = { name: "레몬에이드", price: 4500, onsale: true // onsale 속성을 추가해주었다. }
 
또한 옵션 속성을 사용하여 인터페이스를 정의한다면 인터페이스의 속성들을 모두 가지고 있지 않더라도 에러가 발생하지 않는다.
 
💡
옵션 속성 ?는 인터페이스에 정의된 속성을 선택적으로 사용할 수 있도록 한다.
 
다음은 옵션 속성을 사용하여 인터페이스를 다시 정의하고 위의 에러를 해결한 예시이다.
 
interface Menu { name: string; price: number; onsale?: boolean; // onsale 속성을 옵션 속성으로 지정해주었다. } const lemonade: Menu = { name: "레몬에이드", price: 4500 } // 에러가 발생하지 않는다.
onsale 속성을 옵션 속성으로 지정해줌에 따라 lemonade는 onsale 속성을 가지고 있지 않더라도 에러가 발생하지 않는다.
 
그 다음으로 변수 lemonade가 Menu 인터페이스의 3가지 속성을 모두 가지고 있지만 각 속성의 타입이 일치하지 않는 경우이다.
 
  • 인터페이스의 속성 타입이 일치하지 않는 경우
interface Menu { name: string; price: number; onsale: boolean; } const lemonade: Menu = { name: "레몬에이드", price: 4500, onsale: 1 }
lemonade의 name과 price 속성은 각각 stringnumber 타입을 잘 따르고 있다. 하지만 boolean 타입으로 정의되어야 하는 onsale 속성의 타입은 number로 정의되어 있다. 이렇게 선언된 lemonade는 다음과 같은 에러 메시지를 출력한다.
 
notion imagenotion image
Menu 인터페이스 정의에서 선언한 onsale 속성이 boolean 타입과 일치하지 않는다는 에러메시지이다. onsale 속성의 값을 boolean 타입으로 바꿔줌으로써 에러를 해결할 수 있다.
 
예제를 통해 인터페이스를 정의하고 이를 새로운 변수의 타입으로 지정해 보았다. 이처럼 타입스크립트에서 인터페이스를 사용하여 상호 간 약속을 정의하면, 형식에 맞지 않는 데이터가 선언 됐을 때 이를 쉽게 검출할 수 있다는 장점이 있다.
 
 

6.2.2 함수에서 인터페이스 사용

6.2.2.1 함수의 파라미터를 인터페이스로 지정

앞의
🖥️
5. 함수와 클래스
파트에서를 타입스크립트에서는 함수를 선언할 때 파라미터의 타입을 지정해줄 수 있었다는 것을 떠올려보자.
 
function print(str: string){ console.log(str); }
위 함수에서는 파라미터로 선언된 str의 타입을 string으로 지정해주었다. 이 파라미터의 타입 자리에는 string, numbervoid 등의 기본 타입 외에도 사용자가 만든 인터페이스가 올 수 있다. 그러면 직접 코드를 통해 인터페이스를 선언하고, 이를 함수 파라미터의 타입으로 지정해보도록 하자.
 
interface Menu { name: string; price: number; onsale: boolean; } function Order(item: any) { console.log(item, '주문합니다.'); } Order('핫초코'); // 핫초코 주문합니다. Order(5000); // 5000 주문합니다. Order(true); // true 주문합니다.
위 예시에서 사용했던 동일한 Menu 인터페이스를 선언해주고, 아래는 Order 라는 함수를 선언해주었다. 함수에서 인터페이스의 사용 여부를 비교해보기 위해, 우선 Order 함수의 매개변수 item의 타입을 any 타입으로 지정해주었다. 따라서 어떠한 자료형이 인자로 전달 되더라도 함수가 정상적으로 작동한다. 함수의 실행 결과를 확인하기 위해 다양한 타입(string, number, boolean)의 인자들을 전달해 보았다. 결과가 모두 콘솔에 정상적으로 찍히는 것을 확인할 수 있다. 그렇다면 이제 본격적으로 함수의 파라미터 item의 타입을 Menu 인터페이스로 지정해주면 동작이 어떻게 변화하는지 살펴보도록 하자.
 
interface Menu { name: string; price: number; onsale: boolean; } function Order(item: Menu) { console.log(item, '주문합니다.'); } Order('핫초코'); // 'string' is not assignable to parameter of type 'Menu'. Order(5000); // 'number' is not assignable to parameter of type 'Menu'. Order(true); // 'boolean' is not assignable to parameter of type 'Menu'.
notion imagenotion image
함수의 파라미터 item의 타입을 Menu 인터페이스로 지정해 주었다. 그 결과로 인자(Argument)로 전달된 값들이 Menu 타입의 파라미터에 할당될 수 없다는 오류가 발생한다. 즉, Order 함수에 전달되는 인자는 Menu 인터페이스 형식을 따라 { name: string, price: number, onsale: boolean } 형태를 가져야 한다는 것이다. 그러면 Order 함수의 전달 인자가 Menu 인터페이스 형식을 따르도록 코드를 수정하여 보도록 하자. 인자들이 잘 전달 되었는지를 확인하기 위해 Order 함수의 내용도 수정해주었다.
 
interface Menu { name: string; price: number; onsale: boolean; } function Order(item: Menu) { if (item.onsale === true){ console.log(item.name, '주문합니다.'); console.log(item.price, '원입니다.'); } } Order({name: "레몬에이드", price: 4500, onsale: true}); // 레몬에이드 주문합니다. // 4500 원입니다.
함수 Order에 인자를 전달할 때 {name: "레몬에이드", price: 4500, onsale: true} 형태로 작성해 전달해주어야 오류 없이 실행할 수 있다. 변수 선언 시 인터페이스를 사용했을 때와 마찬가지로 누락된 속성이 있으면 에러가 발생한다.
 
  • 인터페이스의 속성을 모두 가지고 있지 않은 경우
Order({name: "레몬에이드", price: 4500}); // 에러(onsale 속성 누락)
notion imagenotion image
 
해당 파트에서는 함수 선언 시 파라미터의 타입을 인터페이스로 지정해보았다. 이를 통해 함수를 호출할 때 지정해준 인터페이스를 따르는 알맞은 형식의 인자만 받을 수 있도록 하여 사전에 에러를 방지할 수 있다.
 

6.2.2.2 함수의 구조를 인터페이스로 지정

// 함수 선언문 function sum(x: number, y: number): number { return x + y; };
앞의 함수 파트에서 다뤘던 것 처럼 타입스크립트에서 함수를 선언할 때 위의 코드와 같이 함수의 매개변수들의 타입을 지정해주고, 반환값의 타입도 지정해주어야 한다. 이런 함수의 구조를 인터페이스로 만들어 볼 수도 있다. 함수의 구조를 인터페이스로 정의하는 방법은 다음과 같다.
 
interface 인터페이스이름 { (파라미터: 타입): 반환값 타입; }
함수의 구조를 인터페이스로 정의하는 방법
 
// 함수의 구조를 인터페이스로 지정 interface sum_interface { (x: number, y: number): number; }
앞에서 선언한 sum 함수를 인터페이스로 정의해본 코드이다. 함수 선언문에서 함수의 몸통(body)을 구성하는 문(statement)[1]을 제외하고 파라미터의 타입, 반환값의 타입만 지정해준 것이라고 할 수 있다. 따라서 함수 구조를 인터페이스로 정의하는 것은 크게 어렵지 않다. sum_interface 인터페이스를 설명하자면 해당 인터페이스를 타입으로 하는 함수는 파라미터 x와 y를 가지며 그 타입은 number이다. 그리고 그 함수의 반환값 또한 number 타입이어야 한다. 그렇다면 위의 코드를 살짝 변형하여 동일한 인터페이스로 합을 구하는 함수 뿐만 아니라 사칙연산 함수를 구현해 볼 수도 있다.
 
interface Operation { (x: number, y: number): number; } let add: Operation; let sub: Operation; let mul: Operation; let div: Operation; add = (x:number, y:number): number => x + y; sub = (x:number, y:number): number => x - y; mul = (x:number, y:number): number => x * y; div = (x:number, y:number): number => x / y; console.log(add(1,2)); // 3 console.log(sub(1,2)); // -1 console.log(mul(1,2)); // 2 console.log(div(1,2)); // 0.5
파라미터 x,y와 반환값 모두 number 타입을 가지는 Operation 인터페이스가 있다. 그리고 add, sub, mul, div 함수는 모두 Operation 인터페이스를 타입으로 하는 함수이다. 그리고 각각 함수들의 인자와 반환값의 타입이 인터페이스에서 정의된 타입(number)과 동일하기 때문에 오류 없이 연산 결과를 리턴하는 것을 확인할 수 있다. 만일 정의된 인터페이스와 일치하지 않는다면 오류가 발생한다. 다음은 오류가 발생하는 코드이다.
 
interface Operation { (x: number, y: number): number; } let console_add: Operation; console_add = (x:number, y:number): void => console.log(x + y);
notion imagenotion image
인터페이스 선언에서는 함수의 반환값이 number 타입으로 정의되어있지만 console_add 함수는 반환값이 void인 함수이다. 따라서 위와 같은 에러를 나타낸다.
 

6.2.3 클래스에서 인터페이스 사용

클래스가 어떠한 인터페이스를 사용한다고 할 때, implements 키워드를 사용하여 이를 표현한다.
class 클래스명 implements 인터페이스명
interface PersonInterface { name: string; age: number; } class Person implements PersonInterface{ name: string; age: number; constructor(name:string, age:number){ this.name = name; this.age = age; } }
다음 코드에서 Person 클래스는 PersonInterface를 사용하는 클래스이다. 클래스의 프로퍼티들의 타입이 PersonInterface에서 지정되고 있고, 이를 사용하는 Person 클래스는 인터페이스에서 지정한 규칙을 따라야 한다. 만일 프로퍼티의 속성의 이름이나 타입이 인터페이스에서 선언해준 내용과 다른 경우, 에러를 표시한다.
 
  • 인터페이스의 속성을 모두 가지고 있지 않은 경우
interface PersonInterface { name: string; age: number; } class Person implements PersonInterface{ name: string; constructor(name:string){ this.name = name; } }
notion imagenotion image
Person 클래스가 PersonInterface를 정상적으로 사용하고 있지 않다는 메시지를 확인할 수 있다. 그리고 ‘age’라는 속성이 클래스에 필요하다는 내용이다. 인터페이스를 사용하여 규칙을 따르지 않는 클래스의 형식을 잡아낼 수 있다.
 
  • 인터페이스의 속성 타입이 일치하지 않는 경우
interface PersonInterface { name: string; age: number; } class Person implements PersonInterface{ name: string; age: string; constructor(name:string, age:string){ this.name = name; this.age = age; } }
notion imagenotion image
이번에는 Person 클래스의 age 속성을 string 타입으로 변경해보았다. 마찬가지로 클래스의 age라는 프로퍼티가 인터페이스에서 정의된 age 속성에 할당될 수 없다는 에러메시지를 보여준다. string 타입이 number 타입에 할당될 수 없기 때문이다. 이처럼 클래스가 인터페이스의 속성들을 모두 가지고 있더라도 타입이 일치하지 않는다면 에러가 발생한다. 따라서 클래스가 어떤 인터페이스를 따른다면 인터페이스에서 지정한 속성과 타입을 모두 만족해야 한다.
 
 

6.2.4 인터페이스 상속

interface Menu { name: string; price: number; onsale: boolean; } interface SetMenu { name: string; price: number; onsale: boolean; option: string; }
다음과 같은 2개의 인터페이스가 있다고 하자. 자세히 보면 Menu 인터페이스의 속성과 그 타입이 모두 동일하게 SetMenu 인터페이스에도 들어있는 것을 확인할 수 있다. 즉, 불필요한 코드가 반복되고 있는 것이다. 이처럼 하나의 인터페이스의 속성들을 다른 인터페이스가 모두 가지고 있다면 이를 상속받아 확장하여 사용할 수 있다. extends 키워드를 사용하여 기존 존재하는 인터페이스를 확장하여 사용할 수 있는데, 자바스크립트에서 클래스를 상속받는 것과 사용 방법이 비슷하다. SetMenu 인터페이스가 Menu 인터페이스를 상속받으려면 다음과 같이 구현할 수 있다.
interface 상속받는인터페이스 extends 상속해주는인터페이스
인터페이스를 상속받는 방법
interface Menu { name: string; price: number; onsale: boolean; } interface SetMenu extends Menu { option: string; }
위 코드는 앞에서 예시로 작성했던 코드와 동일하게 작동하는 코드이다. 그렇다면 이제 Menu 인터페이스를 상속받아 생성한 SetMenu 인터페이스를 사용해보자.
 
  • 인터페이스의 속성을 모두 가지고 있지 않은 경우
interface Menu { name: string; price: number; onsale: boolean; } interface SetMenu extends Menu { option: string; } const cakeSet: SetMenu = { option: "cake" } // 에러 const cookieSet: SetMenu = { name: "americano", price: 6000, onsale: true, option: "cookie" }
notion imagenotion image
SetMenu 인터페이스는 Menu 인터페이스를 상속하였으므로 Menu 인터페이스의 조건까지 부합해야 한다. cakeSet 변수는 name, price, onsale 속성이 누락되었다는 에러 메시지가 표시된다. 따라서 cookieSet 변수처럼 4가지의 속성을 모두 다 가지고 있으며 각 속성의 타입이 선언과 일치해야 한다.
 
 

6.3 인터페이스와 타입 별칭 비교

타입 별칭이란? 앞서 우리는 3.2.7 타입 별칭 에서 타입 별칭이 무엇인지 알아보았다. 정리하면 타입 별칭은 어떤 형태의 타입에 별칭을 붙이는 것을 말하며, 그 덕에 같은 타입이 반복될 때 별칭을 사용해 코드의 양을 줄이고 가독성을 높일 수 있었다.
 
type Lion = { isCool: boolean; roar: () => void; } const licat:Lion = { isCool: true, roar: () => console.log("으르렁!") }
타입 별칭을 활용한 간단한 예시를 보자. Lion 이라는 객체 타입 별칭을 선언하고 있으며, 그 모양을 정의했다. 이를 아래에서 licat 이라는 변수에 활용했다.
 
이번에는 Lion 을 인터페이스로 정의한 예시를 보자.
interface Lion { isCool: boolean; roar: () => void; } const licat:Lion = { isCool: true, roar: () => console.log("야옹! (사실 고양이)") }
타입 별칭을 사용했을 때와 거의 차이가 없다. 두 경우 모두 Lion 객체 안의 값들에 대한 타입을 정한다는 측면에서 같은 효용성을 제공하고 있다. 그렇다면 타입 별칭과 인터페이스의 차이점은 무엇이 있을까?
 

6.3.1 인터페이스는 선언 병합이 가능하다.

type Icecream = { flavor: string; }
아이스크림의 맛을 담는 Icecream 타입을 외부 패키지에서 가져왔다고 생각해보자. 우리는 이 타입을 활용해 아이스크림의 정보를 출력하는 getIcecreamInfo 함수를 아래처럼 정의할 수 있다.
 
function getIcecreamInfo(icecream: Icecream) { console.log("맛: ", icecream.flavor); }
그런데, 아이스크림의 맛만 담지 않고 가격도 같이 담기로 했다면 어떻게 해야 할까? Icecream 은 외부에서 불러온 인터페이스이므로 변경할 수 없기에 결국 아래처럼 직접 새로운 타입을 선언하고 이를 getIcecreamInfo() 에 바꿔넣어야 한다.
 
type IcecreamWithPrice = { flavor: string; price: number; } function getIcecreamInfo(icecream: IcecreamWithPrice) { console.log("맛:", icecream.flavor); console.log("가격:", icecream.price, "원"); }
위 예제에서는 변경 사항에 대응하기 위해 해야 할 일이 많지 않았지만, Icecream 타입을 사용하는 코드가 많았다면 까다로운 작업이었을 것이다. 그러나, 인터페이스를 사용하면 이런 상황에서도 유연하게 대처할 수 있다. 인터페이스는 선언 병합이 가능하기 때문이다.
 
// 외부 패키지로부터 가져온 인터페이스 interface Icecream { flavor: string; } // 선언 병합 interface Icecream { price: number; } function getIcecreamInfo(icecream: Icecream) { console.log("맛:", icecream.flavor); console.log("가격:", icecream.price, "원"); }
Icecream 을 선언 병합을 활용해 확장해 문제를 간단하게 해결했다. 이처럼 인터페이스는 타입을 중복하여 선언해 확장할 수 있다.
 

6.3.2 IDE 상에서 프리뷰가 다르게 나온다.

코드 에디터별로 상이할 수 있으나, 대부분의 에디터에서는 타입 별칭과 인터페이스 각각의 프리뷰(마우스를 올려놓았을 때 나오는 창)가 다르다.
 
notion imagenotion image
notion imagenotion image
좌측은 인터페이스를 사용한 경우, 우측은 타입 별칭을 사용한 경우이다. 타입 별칭은 타입을 선언하는게 아니라 타입의 이름을 지어준다는 의미를 가지고 있다는 점에서 왜 이런 차이가 생겼는지 알 수 있다.
 
이처럼 타입 별칭을 이용하면 내부에 들어가야 하는 프로퍼티 정보를 확인하기 용이하나, 복잡한 구조의 타입이라면 인터페이스를 사용하는 것이 유용하다.
 

6.3.3 인터페이스는 객체 타입이어야 한다.

type CatFood = "Cheese" | "Tuna" | "Apple"; type Cat = { favoriteFood: CatFood; servent: Human; } interface Human { name: string; master: Cat; }
위 예제에서 Cat 은 타입 별칭으로, Human 은 인터페이스로 선언했다. Cat 타입은 인터페이스로도 정의할 수 있으며, 반대도 마찬가지이다. 그러나 CatFood 처럼 원시형 데이터를 이용한 타입을 정의하는 경우, 인터페이스는 변수 자체에 타입을 지정할 수 없다. 인터페이스로 지정할 타입에는 키 값이 필요하기 때문이다. 따라서 CatFood 를 인터페이스로 바꿔야 한다면 아래처럼 작성해야 한다.
 
interface CatFoodInterface { catFood: "Cheese" | "Tuna" | "Apple"; }
이같은 차이는 아래의 예시로도 확인할 수 있다.
 
interface Human { name: string; age: number; } type Age = Human['age']; const parkShiWooAge: Age = 25; // number type
타입 별칭으로 기존 객체형 타입에 지정된 타입을 재사용한 예시이다. Humanagenumber 즉 원시형 데이터이므로 이는 타입 별칭으로 가져올 수 있다. 반면 인터페이스를 사용하면 아래와 같이 바뀐다.
 
interface Human { name: string; age: number; } interface Age { age: Human['age']; } const ageOfShiWoo: Age = { age: 25 };
 

6.3.4 객체간 교차 타입

타입 별칭과 인터페이스는 객체간 교차 타입을 나타내는 방법이 다르다.
  • 타입 별칭을 이용할 경우
    • type Phone = { size: string[], color: string; } type IPhone = Phone & { series: "iPhone13" | "iPhone14"; } const myPhone: IPhone = { size: ["71.5mm", "147.5mm"], color: "white", series: "iPhone14" }
      Phone 이라는 타입 별칭을 선언하고, 이후 & 연산자로 Phoneseries 프로퍼티를 붙여 확장했다. 이 때, 교차되는 타입들은 인터페이스를 넣어도 무방하다.
       
  • 인터페이스를 이용할 경우
    • interface Phone { size: string[], color: string; } interface IPhone extends Phone { series: "iPhone13" | "iPhone14"; } const myPhone: IPhone = { size: ["71.5mm", "147.5mm"], color: "white", series: "iPhone14" }
      이번에는 각 타입을 인터페이스로 선언하고 extends 키워드를 사용해 기존의 Phone 인터페이스를 확장했다. extends 키워드의 오른편에 놓이는 타입은 인터페이스는 물론, 객체 타입의 타입 별칭도 올 수 있다.