TypeScript - Class
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');