TS에서 일반적인 클래스는 다음과 같이 만들 수 있다.
class Player {
constructor (
private firstName:string,
private lastName:string,
public nickname:string
) {}
}
const lunar = new Player("lunar", "moon", "zentechie");
lunar.firstName = "ddd"; // 에러
여기서 firstName
은 private
이므로, lunar.firstName = "ddd";
에서 에러가 발생한다.
(private
을 가지고 있다면, 임의로 수정을 할 수가 없다.)
JS에서는 이 코드가 아래와 같이 표현된다.
class Player {
constructor(firstName, lastName, nickname) {
this.firstName = firstName;
this.lastName = lastName;
this.nickname = nickname;
}
}
const lunar = new Player("lunar", "moon", "zentechie");
lunar.firstName = "ddd";
보다시피 private
과 public
이 사라졌다. 그리고 this
가 사용됐다.
TS에서는 번거롭게 이러한 this
를 사용하지 않아도 내부적으로 처리해준다.
또한, lunar.firstName = "ddd";
에서 에러가 발생하지 않는다.
추상 클래스는 다른 클래스가 상속받을 수 있는 클래스이다.
직접적으로 추상 클래스의 새로운 인스턴스를 만들 수는 없다. 상속받는 클래스는 extends
를 사용해서 상속받는다.
abstract class User { // 추상 클래스
constructor (
private firstName:string,
private lastName:string,
public nickname:string
) {}
}
class Player extends User {
}
const lunar = new User("lunar", "moon", "zentechie"); // 에러
추상 클래스의 메소드를 알아보자.
abstract class User {
constructor (
private firstName:string,
private lastName:string,
public nickname:string
) {}
private getFullName() { // 추상 메소드
return `${this.firstName} ${this.lastName}`
}
}
class Player extends User {
}
const lunar = new Player("lunar", "moon", "zentechie");
lunar.getFullName() // 에러
보다시피 풀네임을 반환하는 getFullName()
이 메소드이다. 우리는 Player
의 인스턴스에서 getFullName()
을 호출할 수 있다.(→ User
클래스를 상속받았기 때문이다.)
추상 메소드도 접근 제한자를 설정할 수 있는데, 프로퍼티와 마찬가지로 private
으로 설정하게 되면 더 이상 해당 메소드를 접근할 수 없게된다.
즉, private
및 public
이 프로퍼티 뿐만 아니라 메소드에서도 작동한다.
추상 메소드도 알아보자. 추상 메소드는 일반 메소드랑 다르게 선언만 하고 구현은 하지 않는다.
abstract class User {
constructor (
protected firstName:string,
protected lastName:string,
protected nickname:string
) {}
getFullName() {
return `${this.firstName} ${this.lastName}`
}
abstract getNickName():void
}
class Player extends User {
getNickName() {
console.log(this.nickname)
}
}
추상 메소드는 추상 클래스를 상속받는 모든 클래스들이 구현을 해야하는 메소드를 의미한다.
여기서는 Player
클래스가 해당되겠다.
보다시피 추상 메소드인 getNickName()
은 선언만 하고 구현은 하지 않았다.
User
를 상속받는 Player
클래스에서 구현을 진행한다.
✅ 꼭 알아둬야 하는 개념이다.
접근 제한자에 따라서, 어떤 상황에서 접근할 수 있는지 없는지가 달라진다.
구분 | 선언한 클래스 내 | 상속받은 클래스 내 | 인스턴스 |
---|---|---|---|
private | ⭕️ | ❌ | ❌ |
protected | ⭕️ | ⭕️ | ❌ |
public | ⭕️ | ⭕️ | ⭕️ |
type Foods = { name: string, def: string }
type Food = string
type Food = "한식" | "일식" | "중식"
type과는 약간의 차이점이 존재하는데, 인터페이스는 오직 한가지의 용도로만 사용된다.
'오브젝트의 모양을 묘사'
TS에서 오브젝트의 모양을 알려주는 방법은 type
과 interface
2가지 이다.
type Foods = { ... }
interface Foods { ... }
interface
가 가지는 다른 기능들도 살펴보자.
interface
는 상속의 개념을 활용할 수도 있다.interface Foods { name : string }
interface Food extends Foods {}
property
를 축적시킬 수 있다.interface Person {
name:string
}
interface Person {
age:number
}
interface Person {
sex:string
}
const person: Person = {
name:"lunarmoon",
age: 99,
sex: "alien"
} // 3개의 interface가 합쳐진다.
interface는 객체지향 프로그래밍의 개념을 활용해서 디자인되었고, 오로지 오브젝트의 모양을 설명해주는 용도로만 사용된다.
type은
interface
키워드에 비해 좀 더 활용할 수 있는게 많다. → 좀 더 유연하다.
추상 클래스가 있다고 하자. 이를 JS로 변환하게 된다면 추상 클래스 JS파일에 변환되어 남게된다.
하지만 인터페이스를 사용해서 implements
를 한다면, JS로 변환했을 시 코드가 남지 않는다.
(JS에서 인터페이스와 implements를 지원하지 않기 때문이다.)
interface & implements
를 사용해서 코드 최적화를 시킬수 있다.
즉 기능적인 부분에서 abstract
와 interface & implements
는 완전 동일한 기능을 수행하지만, 코드 최적화를 해야한다면 인터페이스를 사용하는 것이 좋다.