공부/TypeScript

TypeScript - Class

도리암 2022. 2. 27. 22:37

What are Classes?

class

  • object를 만드는 blueprint(청사진, 설계도)
  • 클래스 이전에 object를 만드는 기본적인 방법은 function
  • JavaScript에도 class는 es6부터 사용 가능
    • 접근제어자 등 기능이 부족함
  • OOP을 위한 초석
  • TypeScript에서는 클래스도 사용자가 만드는 타입 중 하나
    • JavaScript보다 더욱 강력한 기능 보유

Quick Start - Class

  • class 키워드를 이용하여 클래스를 만들 수 있다.
  • class 이름은 보통 파스칼 케이스로 작성한다.
  • new를 이용하여 class를 통해 object 를 만들 수 있다.
  • constructor를 이요하여 object를 생성하면서 값을 전달할 수 있다.
  • this를 이용해서 만들어진 object를 가리킬 수 있다.
  • JS로 컴파일 되면 es5의 경우 fucntion으로 변경된다.

    예시

    가장 기본적인 형태의 class
    //TypeScript
    

class Person {}

const p1 = new Person();

console.log(p1);

ES6 미만 버전에서 컴파일 하면 다음과 같이 컴파일 된다.
```js
//JavaScript ES5
"use strict";
var Person = /** @class */ (function () {
    function Person() {       // 클래스가 function으로 바뀌어있다.
    }
    return Person;
}());
var p1 = new Person();
console.log(p1);

ES6 이상 버전에서 컴파일 한 경우

"use strict";
class Person {
}
const p1 = new Person();
console.log(p1);

constructor & initialize

  • 생성자 함수가 없으면, 디폴트 생성자가 불린다.
  • 프로그래머가 만든 생성자가 하나라도 있으면, 디폴트 생성자는 사라진다.
  • strict 모드에서는 프로퍼티를 선언하는 곳 또는 생성자에서 값을 할당해야 한다.
  • 프로퍼티를 선언하는 곳 또는 생성자에서 값을 할당하지 않는 경우에는 !를 붙여서 위험을 표시한다.
  • 클래스의 프로퍼티가 정의되어 있지만, 값을 대입하지 않으면 undefined 이다.
  • 생성자에는 async를 설정할 수 없다.

예시

class Person {
  name: string = "Mark";
  age!: number;         // 런타임 상에서 값이 할당이 되면 !를 붙여준다.
  constructor(age?:number){  // constructor를 선언하면 디폴트 생성자는 사라진다.
    if (age === undefined){
      this.age = 20;
    } else {
      this.age = age;
    }

  }

}

const p1 = new Person(38);
const p2 = new Person();
const p3 = new Person();
p2.name = 'kim';
p2.age = 39;             // 런타임 상에서 값을 할당함.
console.log(p1);        // { name: 'Mark', age: 38 }
console.log(p2);        // { name: 'kim', age: 39 }
console.log(p3);        // { name: 'Mark', age: 20 }

접근 제어자(Access Modifiers)

typescript에서는 기본적으로 public이다.
JavaScript에서는 접근 제어자가 없으므로 변수나 함수 이름 앞에 _ 를 붙여서 private를 표시하기도 했다.

  • 접근 제어자에는 public, private, protected가 있다.

  • 설정하지 않으면 public이다.

  • 클래스 내부의 모든 곳에(생성자, 프로퍼티, 메서드) 설정 가능하다.

  • private로 설정하면 클래스 외부에서 접근할 수 없다.

  • 자바스크립트에서 private를 지원하지 않아 오랜 기간동안 프로퍼티나 메소드 이름 앞에 _를 붙여서 표현했다.

    class Person {
    public name: string = "Mark";
    private age!: number;         // 런타임 상에서 값이 할당이 되면 !를 붙여준다.
    public constructor(age?:number){  // constructor를 선언하면 디폴트 생성자는 사라진다.
      if (age === undefined){
        this.age = 20;
      } else {
        this.age = age;
      }
    
    }
    public async init() {
    
    }
    

}

const p2 = new Person();
p2.age = 28 ; // 접근이 안되므로 error가 발생한다.


# initialization in constructor parameters
입력 받은 값을 그대로 같은 이름의 변수에 대입하는 경우,  
매개변수에 접근제어자를 붙여주면 변수 선언을 생략할 수 있다.
```ts
class Person {
  name: string;
  age: number;

  constructor(name: string, age: number){
    this.name = name;
    this.age = age;
  }
}
const p1: Person = new Person("Mark", 39);
console.log(p1.name)     // Mark
class Person {
  constructor(public name: string, public age: number){
  }
}
const p1: Person = new Person("Mark", 39);
console.log(p1)     // {"Mark", 39}

Getters & Setters

Getter: 인스턴스의 내부 변수 값을 가져오는 함수
Setter: 인스턴스의 내부 변수 값을 변경하는 함수

  • 내부 변수를 private로 보호한 뒤 getter와 setter로만 접근하도록 하는 접근제어함수.
  • getter만을 설정해두면 읽기 전용, setter만을 설정해두면 쓰기 전용으로 사용 가능.
  • getter와 setter는 마치 프로퍼티처럼 사용 가능하다.

예제

class Person {
  constructor(private _name: string, private age: number){
  }

  get name() {
    // console.log("get"); // getter를 이용하면 전처리를 해줄 수 있다.
    return this._name;
  }
  set name(n: string) {
    // console.log("set");
    this._name = n + "DO";
  }
}

const p1: Person = new Person("Mark", 39);
console.log(p1.name);     // get을 하는 함수를 getter. 마치 프로퍼티처럼 사용 가능.
p1.name = "Riam"          // set을 하는 함수를 setter
console.log(p1.name);     // 결과: RiamDO

readonly properties

  • readonly로 설정한 속성은 초기화하는 부분에서만 설정이 가능하고 나머지에선 변경이 불가능하다.

예시

class Person {
  public readonly name = "Mark";
  private readonly country: string = 'korea';
  constructor(private _name: string, private age: number){}
  hello() {
    this.country = "china"; // 에러: 변수가 이미 readonly로 할당되어서 변경할 수 없다.
  }
}

const p1: Person = new Person("Mark", 39);
console.log(p1.name);    
p1.name = "Riam"        // 에러: readonly이므로 set할 수 없다.

Index Signatures in class

동적으로 프로퍼티가 들어오는 경우에 고려해 볼 만한 방법.
인터페이스의 optional property와 비슷하다.

예시

// class => object
// a반: {mark: 'male', jade: 'male'}
// b반: {chloe: 'female', alex: 'male', anna: 'female'}

class Students {
  // [index: string]: string;
  [index: string]: "male" | "female"; // 어떤 프로퍼티가 들어오든 이거 두 개 중 하나가 들어와야 한다.

  // park: "male" = "hello"; // 에러. male 속성은 male만 받을 수 있다.
  // park: "female" = "male";  // 에러. female 속성은 female만 받을 수 있다.
  park: "female" = "female";
}

const a = new Students();
a.mark = "male";
a.jade = "male";

console.log(a);   // {mark: 'male', jade: 'male'}

const b = new Students();
b.chloe = "female";
b.alex = "male";
b.anna = "female";

console.log(b);   // {chloe: 'female', alex: 'male', anna: 'female'}

Static Properties & Methods

같은 클래스의 인스턴스 간에 변수 또는 함수를 공유할 수 있도록 만들어준다.
클래스 바깥에서 바로 사용도 가능.

예제

class Person {
  public static CITY = "SEOUL";
  public static hello() {
    console.log("안녕하세요", Person.CITY);
  }
}
const p1 = new Person();
const p2 = new Person();

Person.hello();
console.log(Person.CITY);
class Person {
  public static CITY = "SEOUL";
  public hello() {
    console.log("안녕하세요", Person.CITY);
  }
  public change() {
    Person.CITY = "LA";
  }
}
const p1 = new Person();
p1.hello();   // 안녕하세요 SEOUL

const p2 = new Person();
p2.hello();   // 안녕하세요 SEOUL
p1.change();
p2.hello();   // 안녕하세요 LA

Singletons

static을 이용하면 singleton을 구현할 수 있다.
어디서든 접근이 가능하며 단 하나의 객체를 사용 가능한 형태.

예제

class ClassName {
  private static instance: ClassName | null = null;
  public static getInstance(): ClassName {
    // ClassName 으로부터 만든 object가 있으면 그것을 리턴한다.
    // 없으면 만들어서 리턴.
    if (ClassName.instance === null) {
      ClassName.instance = new ClassName();
    } 
    return ClassName.instance;
  }

  private constructor() {}
}

// const a = new ClassName();
const a = ClassName.getInstance();
const b = ClassName.getInstance(); // a와 b는 하나의 함수를 공유하는 셈.

console.log(a === b); // true

상속(Inheritance)

클래스는 상속이 가능하다.
자식 클래스는 반드시 부모 클래스의 생성자를 super로 호출해야 한다.
super는 constructor의 제일 상단에 기재하는 것이 바람직하다.
부모 클래스의 private 프로퍼티도 자식 클래스에서 접근할 수 있는 방법이 있다.

class Parent {
  constructor(protected _name: string, private _age: number) {}

  public print(): void {
    console.log(`이름은 ${this._name}이고, 나이는 ${this._age}입니다.`);
  }
  protected printName(): void {
    console.log(this._name, this._age) // _age는 private지만 이런 함수를 이용하면 자식 객체에서도 접근 가능.
  }
}
class Child extends Parent {
  public gender = 'male';
  public _name = 'Mark Jr.';  // 프로퍼티 오버라이드.

  constructor(age: number) {

    super("Mark Jr. ",age)    // 자식의 생성자는 부모의 생성자를 호출해줘야 한다. (맨 위에)
    this.printName();
    // 자식 생성자에서는 super를 먼저 호출해줘야한다.
  }

}

const p = new Parent('Mark', 28);
p.print();        // 이름은 Mark이고, 나이는 28입니다.

const c = new Child(5);   // Mark Jr. 5
c.print();        // 이름은 Mark Jr.이고, 나이는 5입니다.

Abstract Classes

abstract 함수는 추상 클래스에서만 사용 가능하다.
abstract 함수는 함수의 구현부를 선언하지 않는다.

abstract class AbstractPerson { // abstract 함수를 사용하려면 추상클래스를 명시해야한다.
  protected _name: string = "Mark";

  abstract setName(name: string): void; // abstract 키워드가 붙으면 구현하지 않는다.

}
// 추상클래스는 new 키워드를 사용할 수 없다.

class Person extends AbstractPerson {
  setName(name: string): void {
    this._name = name;
  }
  // 추상 클래스를 상속받으면 추상 함수를 정의해야한다.

}

const p = new Person();
p.setName('Riam');