public이지만 더 이상 변경할 수 없도록 하려면 프로퍼티를 readonly
로 만들면 된다.
그러면 값은 볼 수 있지만 수정은 불가능해진다.
//단어 클래스
class Word {
constructor(
public readonly term: string, //용어
public readonly definition: string, //정의
){}
}
const kimchi = new Word("kimchi", "한국 음식");
kimchi.definition = "아아아아" //readonly이기 때문에 정의 수정 불가능
type Player = {
nickname: string,
hp: number,
}
//type을 사용하여 타입스크립트에게 오브젝트의 모양 알려줄 수 있다.
const rm : Plyaer = {
nickname: "RM",
hp: 100,
}
type Food = string;
const kimchi: Food = "김치"
type Nickname = string
type Hp = number
type Friends = Array<string>
type Player = {
nickname: Nickname,
hp: Hp,
friends: Friends
}
const rm: Player = {
nickname: "RM",
hp: 100,
friends: ["j-hope", "jin"]
}
//일반적인 string이 아닌 특정 string이 되게 하기
type Team = "red" | "blue" | "yellow"
//이런식으로 concrete 타입의 특정 값을 쓸 수도 있다.
//그러면 팀은 red, blue, yellow, 세가지 옵션 중 하나만 가능!
//다른 문자열 넣으면 다 오류뜸
type Hp = 1 | 5 | 10
//이러면 에이치피 값이 1,5,10 중에 하나만 가능
type Player = {
nickname: string,
team: Team,
hp: Hp
}
const rm: Player = {
nickname: "RM",
team: "yellow",
hp: 10
}
이처럼 type
키워드를 사용하여 타입스크립트에서 만들고 싶은 무수히 많은 종류의 타입을 설명하면 된다.
인터페이스는 타입과 두 가지 차이점은 있지만 거의 비슷하다.
//type
type Player = {
nickname: string,
team: string,
hp: number
}
//interface
interface Member {
nickname: string,
team: string,
hp: number
}
const rm: Member = {
nickname: "RM",
team: "yellow",
hp: 10
}
따라서 TS에게 오브젝트의 모양을 알려주는 방법은 2가지이다.
1. type
2. interface
interface User {
name: string
}
//User 인터페이스 상속받은 Player
interface Player extends User {
}
const rm: Player = {
name: "RM"
}
이처럼 인터페이스는 클래스랑 좀 닮았다.
type User = {
name: string
}
type Player = User & {
}
const rm: Player = {
name: "RM"
}
interface User {
name: string
}
interface User {
nickname: string
}
interface User {
hp: number
}
//다 합쳐서 사용가능
const rm: User = {
name: "RM",
nickname: "dudu",
hp: 100
}
//추상 클래스: 추상 클래스를 상속 받는 하위 클래스가 가질 프로퍼티와 메소드를 지정
abstract class User {
constructor(
protected firstName:string,
protected lastName: string,
){}
//추상 메서드 만들기
//string으로 된 name 받아서 string 반환하기
abstract sayHi(name: string):string
//stirng 반환하기
abstract fullName():string
}
//하위 클래스
class Player extends User {
//추상 메서드 구현하기
fullName(){
return `${this.firstName} ${this.lastName}`
}
sayHi(name: string){
return `Hi, ${name}~👋 My name is ${this.fullName}!`
}
//protected는 추상 클래스로부터 상속 받은 하위 클래스가 프로퍼티에 접근하도록 해준다.
}
const rm = new Player("Kim", "Namjoon")
추상 클래스는 자신의 인스턴스를 만들 수가 없다.
new User()
사용 불가
추상 클래스는 단지 상속받는 클래스가 어떻게 동작해야 할지 알려주기 위해 사용된다.
하위 클래스에서 무엇을 구현해야 할지 알려주기 때문에 추상 클래스는 꽤 좋다. sayHi()
, fullName()
사용해야 한다고 알려준다.
하지만 추상 클래스의 문제점은, 자바스크립트에 abstract의 개념이 없다는 것이다.
User 인스턴스를 직접적으로 만들지 않지만, 컴파일된 JS 파일에는 User 클래스가 존재한다.
그럼 추상클래스를 왜 만드는 걸까?
다른 클래스들이 표준화된 모양, 표준화된 프로퍼티와 메서드를 갖도록 해주는 청사진을 만들기 위해 추상 클래스를 사용한다.
이럴 때 interface를 사용하자.
인터페이스는 가볍다. 인터페이스는 컴파일하면 JS로 바뀌지 않고 아에 사라져 버린다!!
그럼 인터페이스를 사용하여, 클래스가 특정 형태를 따르도록 어떻게 강제할 수 있을까?
interface User {
firstName:string,
lastName: string,
//메서드 만들기
//string으로 된 name 받아서 string 반환하기
sayHi(name: string):string
//stirng 반환하기
fullName():string
}
extends
를 쓰면 자바스크립트로 바뀐다.
인터페이스를 상속할 때는 extends
대신 implements
(구현)이라는 키워드를 사용하면 된다.
그러면 코드가 더 가벼워진다. User 인터페이스가 타입스크립트에서만 존재하게 되므로 추적할수 없게 되기 때문이다.
하지만 TS가 Player 클래스가 User 인터페이스를 상속해야 한다고 알려준다.
따라서 클래스가 원하는대로 행동하고, 원하는 프로퍼티를 가지도록 강제하는 추상 클래스의 특징을 인터페이스를 사용해서도 사용할 수 있게된다.
추상 클래스는 컴파일되면 JS의 클래스로 바껴버렸지만, 인터페이스는 컴파일되어도 JS의 클래스로 바뀌지도 않으면서 추상 클래스의 기능은 사용할 수 있다.
public
으로만 사용할 수 있나 봄...ㅇㅇ?class Player implements User {
//여기에 컨스트럭터 만들기
constructor (
public firstName:string,
public lastName: string,
){}
//인터페이스를 상속할 때는 프로퍼티를 private으로 만들 수 없음
//메서드 구현하기
fullName(){
return `${this.firstName} ${this.lastName}`
}
sayHi(name: string){
return `Hi, ${name}~👋 My name is ${this.fullName}!`
}
}
//Player 인스턴스 생성
const rm = new Player("Kim", "Namjoon")
파일 사이즈 줄이고 싶다면 인터페이스를 사용하면 된다.
보다시피 인터페이스는 고유한 사용처가 있다. 클래스의 모양을 알려준다는 점에서 엄청 유용하다.
그러면서도 추상 클래스를 사용할 때 처럼 자바스크립트 코드로 컴파일되지는 않는다.
이게 인터페이스와 클래스의 차이점이다.
그런데 인터페이스를 상속하는 것의 문제점 중 하나는 private
, protected
프로퍼티를 사용하지 못한다는 점이다.
그리고 추상 클래스에서는 컨스트럭터를 사용할 수 있었는데, 인터페이스에서는 컨스트럭터를 사용할 수 없다.
원한다면 하나 이상의 인터페이스를 동시에 상속할 수 있다.
//인터페이스는 오브젝트나 클래스의 모양을 묘사하도록 해준다.
interface User {
firstName:string,
lastName: string,
//메서드 만들기
//string으로 된 name 받아서 string 반환하기
sayHi(name: string):string
//stirng 반환하기
fullName():string
}
interface Human {
hp: number,
}
//인터페이스 두 개 이상 사용하려면 이렇게 작성하면 된다.
class Player implements User, Human {
constructor (
public firstName:string,
public lastName: string,
public hp: number,
){}
fullName(){
return `${this.firstName} ${this.lastName}`
}
sayHi(name: string){
return `Hi, ${name}~👋 My name is ${this.fullName}!`
}
}
const rm = new Player("Kim", "Namjoon")
클래스를 타입으로 사용할 수 있듯이, 인터페이스를 타입으로 사용할 수 있다.
interface User {
firstName:string,
lastName: string,
//메서드 만들기
//string으로 된 name 받아서 string 반환하기
sayHi(name: string):string
//stirng 반환하기
fullName():string
}
//인터페이스를 타입으로 사용할 수도 있다.
//그리고 리턴 값에도 인터페이스를 타입으로 지정할 수 있다.
const makeUser(user: User): User{
return {
firstName: "kim",
lastName: "namjoon",
sayHi: (name) => "xxx"
fullName: () => "xxx"
} //리턴값에 User 인터페이스를 타입으로 지정하면,
//클래스에서 처럼 new Class() 로 작성안해도 된다. 그냥 인터페이스의 내용물만 넣어주면 된다.
}
makeUser({
firstName: "kim",
lastName: "namjoon",
sayHi: (name) => "xxx"
fullName: () => "xxx"
})
//뭐 이런식으로 인자에 인터페이스 사용하여 오브젝트 모양을 지정해 줄 수도 있다.
추상 클래스 대신 인터페이스를 사용하여 클래스에 특정 프로퍼티나 메서드 상속하도록 강제할 수 있다.
인터페이스는 클래스가 아니지만, 클래스의 모양을 특정할 수 있게 해주는 간단한 방법이다.
이처럼 인터페이스는 오브젝트의 모양을 결정지을 수도 있지만, 클래스의 모양을 특정 짓기도 한다.
인터페이스는 추상 크랠스와 비슷한 보호를 제공하지만, 자바스크립트 파일에서는 보이지 않기 때문에 코드를 더 가볍게 할 수 있다.
만약 추상 클래스를 다른 클래스들이 특정 모양을 따르도록 하기 위한 용도로 쓰고 있다면, 같은 역할을 하는 인터페이스를 사용하는 것이 더 좋다.
그리고 한 클래스에서 여러 개의 인터페이스를 상속할 수도 있다.
클래스처럼 타입으로 사용할 수 있다.
이를 사용하여 아규먼트의 오브젝트 모양을 지정해 줄 수도 있고, 리턴 타입에 사용할 수도 있다.
타입스크립트에게 오브젝트의 모양을 알려주고 싶다면 타입과 인터페이스를 사용할 수 있다.
//타입으로 오브젝트 모양 만들기
type PlayerA = {
name: string
}
const PlayerA: PlayerA = {
name: "rm"
}
//인터페이스로 오브젝트 모양 만들기
interface PlayerB {
name: string
}
const PlayerB: PlayerB = {
name: "rm"
}
그런데 둘의 목적은 동일하지만, 할 수 있는 것이 다르다.
//타입으로 오브젝트 모양 만들기
type PlayerA = {
name: string
}
//✅ 타입 상속하기
//PlayerAA 타입은 PlayerA 타입과 다른 오브젝트를 합친 것
type PlayerAA = PlayerA & {
lastName: string
}
const PlayerA: PlayerAA = {
name: "rm",
lastName: "namjoon"
}
//////////////////////////////////////////////////////
//인터페이스로 오브젝트 모양 만들기
interface PlayerB {
name: string
}
//✅ 인터페이스 상속하기: 객체지향
//Player B를 상속하는 PlayerBB 인터페이스 생성
interface PlayerBB extends PlayerB {
lastname: string
}
const PlayerB: PlayerBB = {
name: "rm",
lastname: "namjoon"
}
결과는 같지만 과정이 좀 다르다.
타입과 인터페이스의 가장 큰 차이점은. 새로운 프로퍼티를 추가할 때 알 수 있다.
//타입으로 오브젝트 모양 만들기
type PlayerA = {
name: string
}
//타입 상속하기
//PlayerAA 타입은 PlayerA 타입과 다른 오브젝트를 합친 것
//연산자 & 사용해야 함
type PlayerAA = PlayerA & {
lastName: string
}
//✅ 프로퍼티 추가하기
//❌ 같은 이름의 타입 만들고 다른 프로퍼티 추가할 수 없음
//type PlayerAA = {
// position: string
//}
const PlayerA: PlayerAA = {
name: "rm",
lastName: "namjoon"
}
//////////////////////////////////////////////////////
//인터페이스로 오브젝트 모양 만들기
interface PlayerB {
name: string
}
//인터페이스 상속하기: 객체지향
//Player B를 상속하는 PlayerBB 인터페이스 생성
interface PlayerBB extends PlayerB {
lastname: string
}
//✅ 프로퍼티 추가하기
//✅ 같은 이름의 인터페이스 만들고 다른 프로퍼티 추가할 수 있음!
interface PlayerBB {
position: string
}
const PlayerB: PlayerBB = {
name: "rm",
lastname: "namjoon"
position: "leader"
}
//인터페이스로 오브젝트 모양 만들기
interface PlayerB {
name: string
}
//굳이 상속하지 않고 추가해줘도 괜찮음
interface PlayerB {
lastname: string
}
interface PlayerB {
position: string
}
const PlayerB: PlayerB = {
name: "rm",
lastname: "namjoon"
position: "leader"
}
type PlayerA = {
firstName: string
}
class User implements PlayerA {
constructor(
public firstName: string
){}
}
//////////////////////////////////////
interface PlayerB {
firstName: string
}
class User implements PlayerB {
constructor(
public firstName: string
){}
}
📚 타입스크립트 커뮤니티에 따르면
클래스
나오브젝트의 모양을 정의
하고 싶으면인터페이스
를 사용하고다른 모든 경우
에는타입
을 쓰라고 한다고 한다.
그러니까 타입스크립트에게 오브젝트의 모양을 알려주기 위해서는 인터페이스를 사용하도록 하자~