class Player {
//생성자 함수를 만들어준다.
constructor(private firstName: string, protected lastName: string, public nickname: string
) {}
}
const nico = new Player("nico", "las", "니꼬");
nico.firstName
//--> firstName은 private로 수정 불가능하기 때문에 컴파일되지 않는다.
TypeScript에서만 사용할 수 있는 TypeScript만의 보호기능(JavaScript에서는 사용 불가)
private
키워드 : 선언한 클래스 내에서만 접근 가능protected
키워드 : 선언한 클래스 내 + 상속받은 클래스 내에서 접근 가능public
키워드 : 모두 접근 가능.public
은 생략 가능하다(즉 public이 기본값)- property 뿐만 아니라 method(클래스 안에 존재하는 함수)에서도 작동한다.
class Player {
//생성자 함수를 만들어준다.
constructor(firstName, lastName, nickname) {
this.firstName = firstName;
this.lastName = lastName;
this.nickname = nickname;
}
}
nico.firstName
//--> firstName은 private로 수정 불가능해야 하는데, 수정 가능하다. 즉 JS에서는 private의 보호기능이 제공되지 않는다.
TypeScript의 private
, public
같은 보호기능은 제공되지 않는다.
❗ 즉
private
,protected
,public
키워드는 TypeScript에서만 보여진다.
추상클래스란 다른 클래스에 상속할 수 있는 클래스로, 직접 새로운 인스턴스를 만들 순 없다.
⇒ 즉 추상클래스는 오직 다른 곳에서 상속할수만 있는 클래스를 말한다.
//여기서 User는 추상클래스이다.
abstract class User {
constructor(
private firstName: string,
private lastName: string,
public nickname: string
) {}
}
class Player extends User {
}
const nico = new Player("nico", "las", "니꼬"); // ⭕️
const nico = new User("nico", "las", "니꼬"); //❌ : 추상클래스는 새로운 인스턴스를 만들수 없다.
즉 User는 직접 인스턴스를 생성할 수 없고, 다른 클래스에 상속만 가능하다.
❗ 추상 클래스의 한계
- JavaSCript에선
추상클래스
라는 개념이 없기 때문에 → 추상클래스가 일반 클래스로 변환된다.
//User : 추상클래스
abstract class User {
constructor(
private firstName: string,
private lastName: string,
public nickname: string
) { }
//추상 클래스 속 메서드 만들기
getFullName() {
return `${this.firstName} ${this.lastName}`
}
}
class Player extends User {
}
const nico = new Player("nico", "las", "니꼬");
nico.getFullName();
추상 메서드
(Abstact method)만들기Call signature
만 작성한다.//User : 추상클래스
abstract class User {
constructor(
private firstName: string,
private lastName: string,
private nickname: string
) {}
//getNickname : 추상 메서드
abstract getNickname(): void; // 추상메서드는 Call signature만 작성한다.
//추상 클래스 속 메서드 만들기
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
class Player extends User {
//getNickname 메서드를 구현하지 않으면 컴파일 되지 않는다.
getNickname() {
/*property를 private로 만들면, 상속받은 클래스에서도 해당 property에 접근할 수 없다.
--> 따라서 nickname이 private로 만들어졌기 때문에 this로 접근할 수 없다
- 위 private nickname -> protected nickname 로 수정하면 this로 접근할 수 있다. */
console.log(this.nickname); //컴파일 되지 않음
}
}
const nico = new Player("nico", "las", "니꼬");
nico.getFullName();
❗ 상속받은 클래스에 추상메소드가 있다면 → 자식 클래스에도 추상 메소드를 구현해주어야 한다.
키워드 | 선언한 클래스 내 | 상속받은 클래스 내 | 인스턴스 |
---|---|---|---|
private | ⭕️ | ❌ | ❌ |
protected | ⭕️ | ⭕️ | ❌ |
public | ⭕️ | ⭕️ | ⭕️ |
추상 클래스의 프로퍼티가
private
이라면 → 추상 클래스를 상속받은 클래스에서도 해당 프로퍼티에 접근할 수 없다.
추상 클래스의 프로퍼티가protected
이라면 → 추상 클래스를 상속받은 클래스에서 해당 프로퍼티에 접근 가능하다.
단,private
,protected
모두 클래스/인스턴스 밖에서는 접근 불가능하다.
class Dict {
constructor(
private : words :Words
) {}
}
class Dict {
constructor(words) {
this.words = words;
}
}
: constructor에 인자로 넣어 constructor가 지정해주길 바라는게 아닐 경우
type Words = {
// 프로퍼티의 이름을 모르지만 타입만 알때
[key: string]: string;
};
class Dict {
//1. words를 initializer(초기화) 없이 선언한다.
private words: Words
//2. constructor에서 수동으로 초기화 해준다.
constructor() {
this.words = {};
}
}
class Dict {
constructor() {
this.words = {};
}
}
type Words = {
// 프로퍼티의 이름을 모르지만 타입만 알때
[key: string]: string;
};
//사전 클래스
class Dict {
//1. words를 initializer(초기화) 없이 선언한다.
private words: Words;
//2. constructor에서 수동으로 초기화 해준다.
constructor() {
this.words = {};
}
//단어 추가하는 메서드
add(word: Word) {
//! 클래스(Word)를 타입처럼 사용할 수 있다. --> 즉 add의 파라미터가 Word클래스의 인스턴스이길 바라면 Word클래스를 타입으로 사용할 수 있다.
//주어진 단어가 아직 사전에 존재하지 않다면
if (this.words[word.term] === undefined) {
this.words[word.term] = word.def;
}
}
// term을 사용해 def를 찾는 메서드
def(term: string) {
return this.words[term];
}
}
//단어 클래스
class Word {
constructor(
public term: string, //term = 용어
public def: string
) {}
}
const kimchi = new Word("kimchi", "한국음식");
const dict = new Dict();
dict.add(kimchi); // undefined
dict.def("kimchi"); // "한국음식"
❗ 메서드의 파라미터가 해당 클래스의 인스턴스이길 바라면 → 해당 클래스를 타입처럼 사용할 수 있다.
type Words = {
// 프로퍼티의 이름을 모르지만 타입만 알때
[key: string]: string | string[]
};
//사전 클래스
class Dict {
//1. words를 initializer(초기화) 없이 선언한다.
private words: Words;
//2. constructor에서 수동으로 초기화 해준다.
constructor() {
this.words = {};
}
//🔥 단어 추가하는 메서드
add(word: Word) {
//! 메서드에서 클래스(Word)를 타입처럼 사용할 수 있다.
//주어진 단어가 아직 사전에 존재하지 않다면
if (!this.words[word.term]) {
this.words[word.term] = word.def;
}
}
//🔥 term을 사용해 def를 찾는 메서드
find(term: string) {
return this.words[term];
}
//🔥 단어 삭제하기
removeWord(term: string) {
delete this.words[term];
}
//🔥 단어 이름 업데이트 하기
updateWord(oldTerm: string, newTerm: string) {
if (this.words.hasOwnProperty(oldTerm)) {
this.words[newTerm] = this.words[oldTerm];
delete this.words[oldTerm];
}
}
//🔥 사전에 저장된 단어의 개수
size() {
return Object.keys(this.words).length;
}
//🔥 사전에 저장된 모든 term과 뜻 출력
all() {
for (let [key, value] of Object.entries(this.words)) {
return `${key} : ${value}`
}
}
}
//각 단어에 대한 클래스
class Word {
constructor(
public term: string, //term = 용어
public def: string | string[]
) {}
//🔥 단어의 이름과 뜻을 출력하는 메서드
toWord() {
return `${this.term} : ${this.def}`;
}
//🔥 단어의 정의 추가
addDef(newDef: string) {
if (typeof this.def === "string") {
this.def = [this.def, newDef];
} else {
this.def = [...this.def, newDef];
}
}
//🔥단어의 정의 수정
updateDef(oldDef : string, newDef: string) {
if(typeof this.def === 'string') {
if(oldDef === this.def) this.def = newDef
} else {
this.def.filter(el => el !== oldDef);
this.def.push(newDef);
}
}
}
/*------- 출력 -------*/
// class Word
const kimchi = new Word("kimchi", "한국음식");
const tang = new Word("연근 갈비탕", "중국의 음식");
const sushi = new Word("스시", "일본의 음식");
kimchi.addDef("고춧가루로 배추를 버무려 숙성 및 발효시킨 음식");
kimchi.toWord(); // kimchi: 한국의 음식,고춧가루로 배추를 버무려 숙성 및 발효시킨 음식
tang.toWord(); // 연근 갈비탕: 중국의 음식
sushi.updateDef("일본의 음식", "밥을 뭉쳐놓고 그 위에 재료를 얹어낸 음식");
sushi.toWord(); // 스시: 밥을 뭉쳐놓고 그 위에 재료를 얹어낸 음식
//class Dict
const dict = new Dict();
dict.add(kimchi); // undefined
dict.add(tang); // undefined
dict.add(sushi); // undefined
dict.find("kimchi"); // "한국음식"
dict.all();
dict.find("kimchi"); // (2) ['한국의 음식', '고춧가루로 배추를 버무려 숙성 및 발효시킨 음식']
dict.size(); // 3
dict.updateWord("kimchi", "김치");
dict.removeWord("연근갈비탕")
구분 | 클래스 | 인터페이스 |
---|---|---|
프로퍼티와 메서드를 가질 수 있다. | ⭕️ | ⭕️ |
직접 인스턴스 생성 가능 | ⭕️ | ❌ |
타입으로 사용 가능 | ⭕️ | ⭕️ |
메서드 | - 일반 메서드 | |
- 추상메서드(abstract 키워드 사용) | 모두 추상메서드(abstract 키워드 사용❌) | |
JS로 컴파일 | ⭕️ | ❌ |
interface User {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
}
//implements는 JS엔 없는 단어로, 코드가 좀 더 가벼워진다.
class Player implements User {
constructor(
//인터페이스를 상속할 때엔 → property는 private,protected로 만들 수 없다.
public firstName: string,
public lastName: string
) { }
fullName() {
return `${this.firstName} ${this.lastName}`
}
sayHi(name: string) {
return `Hello ${name}. My name is ${this.fullName()}`;
}
}
❗ 인터페이스를 상속할 때 → property는
private
,protected
으로 만들 수 없다.
JS로 컴파일 시 추상클래스로 작성한 코드가 일반클래스로 변환되는 문제를 개선할 수 있다.
JS로 컴파일 시 인터페이스로 작성한 코드는 사라지기 때문에 파일 크기가 줄어든다.
하나 이상의 인터페이스를 동시에 상속할 수 있다.
interface Human {
age :number
}
class Player implements User, Human {
constructor(
//인터페이스를 상속할 때엔 → property는 private,protected로 만들 수 없다.
public firstName: string,
public lastName: string
public age: number
) { }
...
}
private
, protected
으로 만들 수 없다.interface User {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
}
function makeUser(user: User) {
return "hello"
}
makeUser({
firstName: "nico",
lastName: "las",
fullName : () => "xx",
sayHi: (name) => "string"
})
클래스나 오브젝트의 모양을 알려주고 싶을 때 사용
& 추상클래스를 대체할 수 있다.
인터페이스를 상속시키는 방법이 좀 더 직관적이기 때문에 → 규모가 큰 프로젝트에서는 인터페이스를 많이 사용한다.
타입 alias나 특정값으로 타입을 제한 하는 경우에는 → 타입을 사용한다.
Type
type PlayerA = {
name : string
}
class User implements PlayerA {
constructor(
public name :string
)
}
Interface
interface PlayerB {
name : string
}
class User implements PlayerB {
constructor(
public name :string
)
}
상속방법
Type
type PlayerA = {
name :string
}
type PlayerAA = PlayerA & {
lastName: string;
};
const playerA: PlayerAA = {
name: "nico",
lastName : "las"
}
Interface
interface PlayerB {
name: string;
};
interface PlayerBB extends PlayerB{
lastName: string;
};
const playerB: PlayerBB = {
name: "nico",
lastName: "las",
};
interface PlayerB {
name: string;
};
interface PlayerB {
lastName: string;
};
const playerB: PlayerB = {
name: "nico",
lastName: "las",
};
❗ 새 프로퍼티 추가를 위해 타입은 다시 선언 될 수 ❌ ↔️ 인터페이스는 다시 선언 가능(항상 상속 가능)
interface SStorage<T> {
//3. 인터페이스에서 제네릭을 받아 사용한다.
[key:string] : T
}
//1. 제네릭을 클래스로 보내고
class LocalStorage<T> {
//2. 클래스는 해당 제네릭을 인터페이스로 보내준다.
private storage: SStorage<T> = {}
set(key:string, value:T){
this.storage[key] = value;
}
get(key:string) :T {
return this.storage[key]
}
remove(key:string){
delete this.storage[key]
}
clear(){
this.storage = {}
}
}
const stringsStorage = new LocalStorage<string>()
stringsStorage.get("key") //get(ket:string) :string --> 즉 제네릭 <T>의 타입(placeholder)이 string 타입(concrete)이 된다.
stringsStorage.set("hi", "how are you")
const booleansStorage = new LocalStorage<boolean>()
booleansStorage.get("xxx")
booleansStorage.set("hi", false)