type은 정말 유용한 존재였다.
// 1, 2
type Health = number;
// 3
type Team = "red" | "blue" | "yellow";
type Player = { nickname: string; team: Team; health: Health };
const rhino: Player = {
nickname: "rhino",
team: "yellow",
health: 1,
};
이런 식으로 말이다! type 키워드는 활용할 것이 많다.
그렇다면 type Player = { ... };
부분에 type
지우고 interface
을 넣은다음 =도 지운다면? 이렇게 된다.
interface Player {
nickname: string;
team: Team;
health: Health;
}
바로 여기서 interface
가 나온다.
오로지 오브젝트 모양을 타입스크립트에게 설명해 주기 위한 키워드
즉, type
보다 활용도는 떨어지지만 간단하게 나타낼 때 좋다.
type Player = {
nickname: string;
team: Team;
health: Health;
}
interface Player {
nickname: string;
team: Team;
health: Health;
}
위 두개는 같다. 진짜 객체의 타입 설정에 유용하게 쓰일 수 있는 것.
인터페이스의 또 다른 특징으로는 속성(Property)들을 ‘축적’시킬 수 있다는 것이다.
interface User {
name: string;
}
interface User {
lastName: string;
}
interface User {
health: number;
}
const rhino: User = { name: "rhino", lastName: "n", health: 10 };
interface
가 설정하지 못한다.type Team = "red" | "blue" | "yellow";
type Health = number;
// X
interface Team = "red" | "blue" | "yellow";
interface Hello = string;
type
type UserA = {
firstName: string;
};
type UserAA = UserA & {
lastName: string;
};
// 이건 불가능, 이미 선언되어서 중복이 안됨
type UserAA = {
health: number;
};
interface
interface UserB {
firstName: string;
}
interface UserBB extends UserB {
lastName: string;
}
// 가능! UserBB에 쌓였음
interface UserBB {
health: number;
}
// 심지어 이렇게도 가능
interface UserB {
lastName: string;
health: number;
}
이런 활용도의 차이가 있다. 그냥 적기만 하면 합쳐주니 확장성을 고려한다면 인터페이스가 더 낫다.
type User = {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
};
interface User {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
}
이런 식으로 쓸 수 있다.
그렇다면 굳이 Interface
를 쓰는 이유는 무엇일까?
객체 지향과 비슷한 문법을 사용해 extends
로 상속받을 수 있기도 하고 익숙하기 때문이다.
interface User {
name: string;
}
interface Player extends User {}
const rhino: Player = {
name: "rhino",
};
이렇게 상속도 받고이런 식으로 읽기 전용도 만들 수 있어 클래스형 컴포넌트랑 비슷하다.
물론 &
를 이용해서 type
키워드 간에도 상속을 받을 수 있긴 하다.
type
은 좀 더 다재다능하고 유연해서 아무데서 쓰여도 되지만 Interface
는 객체지향의 디자인을 따와 객체의 설명해주기 때문에 읽기도 편하고 type
과는 분리되어 그 객체의 타입과 내용에 더 집중할 수 있다. 다시 말해 특정 모양을 따르도록 만든 틀이라면 interface
를 쓰는 것이 여러모로 편리하고 좋다는 얘기.
위의 차이에서 보았듯이 인터페이스는 중복을 허가하니 나중에 타입의 수정이나 병합을 가져온다면 최대한 인터페이스를 쓰는 것이 추후의 일에 더 좋을 것이다.
저번에 배웠던 추상 클래스도 Interface와 접목시킬 수 있다.
abstract class User {
constructor (
protected firstName:string,
protected lastName:string
) {}
abstract sayHi (name:string):string
abstract fullName():string
}
여기 추상클래스와 추상화를 가진 메소드 2개가 있다. 저번에서 배운 것처럼 User
를 상속받으려면 청사진만 있는 sayHi
, fullName
메소드를 무조건 구현해야하고, firstName
lastName
을 쓸 수 있다.
이제 구현해보자
abstract class User {
constructor(
protected firstName: string,
protected lastName: string
) {}
abstract sayHi(name: string): string;
abstract fullName(): string;
}
class Player extends User {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
sayHi(name: string) {
return `Hello ${name}. My name is ${this.fullName()}`;
}
}
이렇게 만들어서 구현할 수 있다. 추상 클래스 특징으로 User
의 인스턴스, 즉 직접적으로 구현할 수 없고, Player
같은 상속 받은 클래스가 수많이 나오고, 수많은 인스턴스들이 나올 것이다.
저번에 배웠듯이 User
는 JS로 변환될 것이다.
이런 식으로 변환되는 걸 보니 의문이 생긴다. User
를 직접적으로 만지지도 않을 거고, 상속받고 파생되는 클래스와 인스턴스로 쓸 건데 JS로 컴파일 되도 남아있나? 없앨 수 있는 방법이 있을까?
바로 이때 Interface를 쓰는 것이다!! 인터페이스는 JS로 컴파일되면 없어지고, 클래스보다 훨씬 가벼우면서도 교체가 가능한 점이 있어 이를 이용하면 파일 사이즈도 줄어들고 좋다.
implements
라는 키워드가 핵심이다.
interface User {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
}
class Player implements User {
constructor(
public firstName: string,
public lastName: string
) { }
fullName() {
return `${this.firstName} ${this.lastName}`;
}
sayHi(name: string) {
return `Hello ${name}. My name is ${this.fullName()}`;
}
}
interface
로 바뀜implements
키워드를 써서 User
를 구현해야하는(추상클래스 상속) Player
클래스 생성public
으로 받았다. 인터페이스를 상속받을 땐 다른 레벨이 아닌 public
로만 받아야한다.그러면 결과물이 이렇게 나온다. 아까완 달리 JS로 Player
만 나오는 걸 확인할 수 있다. 이러면 클래스가 하나 적어지는 등의 코드양이 줄어드니 좋다!
또 2개 이상의 인터페이스를 상속받을 수 있다.
interface User {
...
}
interface Human {
health: number;
}
class Player implements User, Human {
constructor(
public firstName: string,
public lastName: string,
public health: number
) {}
...
}
이런 식으로 받을 수 있다. 되게 유용하다. 나중에 패턴의 병합이나 수정이 들어온다면 이렇게 상속받으면 된다.
함수형은 더 쉽다. 직관적이고 사실 리액트에 쓴다면 함수형에 많이들 쓸 것이다. 나는 이렇게 많이 썼다.
// 파라미터에 User 적용
function makeUser(user: User) {
return "Scuccess!";
}
// return까지 User 타입으로
function makeUserReturn(user: User): User {
return { firstName: "nico", lastName: "las", fullName: () => "Xx", sayHi: (name) => "string" };
}
makeUser({ firstName: "nico", lastName: "las", fullName: () => "Xx", sayHi: (name) => "string" });
위에서 봤던 것처럼 추상화가 적용되어 파라미터나 리턴 값에 User
의 모양과 다르다면 오류가 뜰 것이다. 추상 클래스와 비슷하게 모양을 정하는 것이 적용된다는 것! 이렇게 연결되는 것이다.
타입스크립트 상세하게 적어주셔서 감사합니다 예습하고가요 !!