Typescript - 8

hoin_lee·2023년 7월 28일
0

TIL

목록 보기
206/236

Class - typescript에서

기존 javascript에서 배운것과 개념같은 경우랑은 다 같다. 다만 단축어도 있고 타입을 지정해주는 경우를 알아보자

class Player{
  first:string;
  last:string; // 이와 같이 미리 초기화와 같이 타입을 지정해준다.
  constructor(first:string, last:string){
    this.first = first;
    this.last = last;
  }
}
const elton = new Player("Elton","Steele")

처음 생성자를 만들면 this에는 에러가 생성된다. typescript에게 알리지 않고는 프로퍼티와 생성자를 초기화 할 수 없다.
아래 생성자에서 this.first : string = first이처럼 하면 안되고 꼭 위 코드처럼 typescript에게 미리 알려주자

클래스 내 필드

class Player{
  first:string;
  last:string;
  score = 0 ;
  constructor(first:string, last:string){
    this.first = first;
    this.last = last;
  }
}
const elton = new Player("Elton","Steele")

이전에 score를 javascript에서 만들때와 동일하다 그냥 바로 설정해주면 되고 생성자에서 만들고 싶다면 firstlast처럼 미리 타입을 알려주고 아래 생성자에서 추가하면 된다.

readonly 제어자

class Player{
  readonly first:string;
  readonly last:string;
  score = 0 ;
  constructor(first:string, last:string){
    this.first = first;
    this.last = last;
  }
}
const elton = new Player("Elton","Steele")
elton.first = "elton" // readonly이기때문에 에러 발생

이전 typescript그 구문을 연습할 때 햇떤 readonly 가 클래스 내에서도 충분히 활용 가능하다

public 제어자

javascript와 마찬가지로 기본적으로 모두 public 프로퍼티로 간주한다. 마찬가지로 firstlast, score느 ㄴ어디에서나 엑세스 할 수 있다.
물론 이는 위처럼 readonly를 통해 변경 못하게 할 수 있지 않나요? 하고 생각할 수 있다.
하지만 액세스가 가능하다는 게 확연히 다르다
물론 기본 값이기 때문에 필수는 아니지만, 다른 개발자 동료가 클래스 외부에서 변경 할 수 있도록 public이라고 명시를 해놓는 것도 좋다

class Player{
  public readonly first:string;
  public readonly last:string;
  public score = 0 ;
  constructor(first:string, last:string){
    this.first = first;
    this.last = last;
  }
}
const elton = new Player("Elton","Steele")
elton.first //엑세스 가능

private 제어자

프로퍼티나 메서드 앞에 붙이면 된다.

class Player{
  public readonly first:string;
  public readonly last:string;
  private score = 0 ;
  constructor(first:string, last:string){
    this.first = first;
    this.last = last;
  }
}
const elton = new Player("Elton","Steele")
elton.score // score가 private이기 때문에 엑세스 불가

javascript 에서는 #으로 진행했는데 private이 좀 더 명확한 코드처럼 보인다
물론 typescript에서 es 버전을 맞춘 후 #score로 적어도 똑같이 인식한다.
하지만 javascript에서 컴파일링 하고 실행 될때는 사라지는 부분이라 동작은 한다. 이부분은 주의하자

typescript 단축 구문

위와 같이 먼저 프로퍼티와 타입을 선언하는 방식은, 많은 구문과 추가 항목이 필요하게 된다.
그래서 단축구문을 사용하는데

class Player{
  private score:number = 0;
  constructor(public first:string, public last: string){}
}
const elton = new Player("Elton","Steele")

단축구문으로 this.first=first를 적지 않아도 파라미터가 전달될 때 단축돼서 들어간다
이는 파라미터 프로퍼티라고도 부른다

Getter와 Setter

class Player{
  private _score:number = 0;
  constructor(public first:string, public last: string){}
  
  get fullName():string { 
 	return `${this.first} ${this.last}`
  }
  get score():number {
    return this._score;
  }
  set score(newScore:number) { // typescript는 setter에 대해 반환 타입 애너테이션을 허용 X
  if(newScore <0) {
    throw new Error("Score must be positive")
  }
    this._score = newScore;
  }
}
const elton = new Player("Elton","Steele")
elton.fullName // 똑같이 프로퍼티 취급한다
elton.score;
elton.score = 99;

물론 typesciprt는 여기서 음수를 입력한다 하더라도 바로 인식하지는 못한다. 런타임 환경에서 걸리는 조건이기 때문

protected 제어자

Protected는 상속 작업 때 사용된다
하위클래스가 아닌 슈퍼 클래스를 만들어서 해보자

class Player{
  private _score:number = 0;
  constructor(public first:string, public last: string){}
  
  get fullName():string { 
 	return `${this.first} ${this.last}`
  }
  get score():number {
    return this._score;
  }
  set score(newScore:number) {
  if(newScore <0) {
    throw new Error("Score must be positive")
  }
    this._score = newScore;
  }
}
class SuperPlayer extends Player { 
  constructor(powers){
    this.powers = powers; 
  }
  public isAdmin: boolean = true;
  maxScore(){
    this._score = 9999999; // 에러발생 
  }
}

const elton = new Player("Elton","Steele")

여기서 _score 프로퍼티는 프라이빗이며 Player클래스만 액세스 할 수 있다는 에러가 생긴다.
프라이빗을 사용하면 그 클래스 안에서만 액세스 할 수 있기 때문!
해당 클래스를 상속하거나 확장한 자식 클래스에도 적용되지 않는다
하지만 _scoreprivateprotected로 변경하면 외부에서 _score에 액세스하는 게 불가능하지만 자식 클래스는 제외된다!

class Player{
  protected _score:number = 0;
  constructor(public first:string, public last: string){}
  
  get fullName():string { 
 	return `${this.first} ${this.last}`
  }
  get score():number {
    return this._score;
  }
  set score(newScore:number) {
  if(newScore <0) {
    throw new Error("Score must be positive")
  }
    this._score = newScore;
  }
}
class SuperPlayer extends Player { 
  constructor(powers){
    this.powers = powers; 
  }
  public isAdmin: boolean = true;
  maxScore(){
    this._score = 9999999; // 에러 없음
  }
}

const elton = new Player("Elton","Steele")

결론적으로 public은 어디서나 액세스 가능하고 private은 정의된 해당 클래스에서만 protected는 정의된 클래스와 그로부터 상속한 모든 클래스에서 액세스 할 수 있다

인터페이스와 클래스

interface를 활용해 규칙을 만들고 여러 클래스가 생성되지만 interface의 규칙을 따르게 만들 수 있다.
implements를 통해 적용하며 ,로 여러개를 쓸 수 있다. 하지만 여러 interface를 쓰게될 경우 후에 상속을 통해 interface를 리펙토링 해야 할 상황이 오니 너무 마구잡이로 추가해서 사용하진 말자

interface Colorful {
  color:string;
}

interface Printable {
  print():void
}

class Bike implements Colorful { 
  constructor(public color:string){}
}
class Jacket implements Colorful, Printable {
  constructor(public brand:string,public color:string){}
  
  print(){
    console.log(`${this.color} ${this.brand} jacket`);
  }
}

const bike1 = new Bike("red")

const jacket1 = new Jacket("Prada","black")

위와 같이 애너테이션처럼 그것만 존재한다 식의 규칙이 아닌 implementsinterface의 규칙을 포함하고 있는지, 즉 따르는지 확인한다.

abstract 클래스

abstract를 붙일경우 typescript에서는 의미를 가지는데
1. 이 자체로는 더이상 새 클래스를 만들 수 없다

  • Cat을 인스턴스화하려고 하면 abstract 클래스의 인스턴스는 만들수 없다고 뜬다(new 클래스() 사용시 에러발생)
    ?? 그럼 왜 class를 만들지??란 생각이 생길 수 있다.

abstract클래스는 패턴을 정의하고 자식 클래스에서 시행돼야 하는 메서드를 정의하는 데 사용된다

abstract class Employee {
  constructor(public first:string, public last: string){}
  abstract getPay():number;
  greet(){
    console.log("HELLO!")
  }
}

여기서 getPay()메서드는 이 클래스에 존재하지 않는다.
Employee를 확장하는 모든 클래스에 존재해야 한다고 말하고 있는 것이다.
abstract 클래스에는 기능과 데이터도 있지만 이 클래스를 확장하려면 getPay 메서드를 반드시 시행해야 한다.

abstract class Employee {
  constructor(public first:string, public last: string){}
  abstract getPay():number;
  greet(){
    console.log("HELLO!")
  }
}

class FullTimeEmployee extends Employee {
  getPay():number{
    return 3000;
  }
}

class PartTimeEmployee extends Employee {
  getPay():number{
    return 1000;
  }
}

이처럼 abstract키워드를 사용해 abstract 클래스임을 표시한다.
그러면 더이상 인스턴스화 할 수 없게 되지만 메서드는 abstract로 표시할 수는 있고 원하는 만큼 만들 수도 있다.

이는 하위 클래스에 모두가 getPay라는 메서드를 시행해야 한다는 뜻이다
단순히 기능을 상속할 뿐만 아니라 반드시 따라야 할 제약과 규칙도 상속하고 있는 것이다.

abstract class Employee {
  constructor(public first:string, public last: string){}
  abstract getPay():number;
  greet(){
    console.log("HELLO!")
  }
}

class FullTimeEmployee extends Employee {
  constructor(first:string, last: string,private salary:number){
    super(first,last) // 부모 클래스로부터 가져온다
  }
  getPay():number{
    return this.salary;
  }
}

class PartTimeEmployee extends Employee {
  constructor(first:string, last: string,private hourlyRate:number,private hoursWorked:number){
    super(first,last); // 부모 클래스로부터 가져온다
  }
  getPay():number{
    return this.hourlyRate * this.hoursWorked;
  }
}

const betty = new FullTimeEmployee("Betty", "White", 9500)
const bill = new PartTimeEmployee("Bill", "Billerson", 24, 1000)

이런식으로 변경해도 충분히 가능하다!
super를 통해 부모 클래스의 firstlast를 가져왔으니 하위 자식에게선 public first:string처럼 입력하지 않아도 된다

인터페이스와의 차이점은 abstract 클래스처럼 확장될 때 자동으로 얻게 되는 추가 기능은 포함되지 않는다(위의 예시는 greet)

profile
https://mo-i-programmers.tistory.com/

0개의 댓글