-
[Typescript] Class(클래스)카테고리 없음 2021. 2. 24. 04:25반응형
😏 서론
Javascript 클래스를 복습하는 포스팅을 통해 공부했을 뿐만 아니라, 프로토타입 개념을 별도로 정리하면서 자바스크립트만의 클래스를 나름대로 이해하게 된 계기였다.
Typescript 역시 ES6에서 도입된 Class 문법을 활용 가능하며, 좀 더 강력한 지원이 가능하다고 한다.
양이 많은 만큼 긴 포스팅이 될 것 같다!!!! 🤯🤯🤯🤯
💙 Class 란?
Javascript 와 마찬가지로, 클래스 형식을 직관적으로 작성 가능한 Syntactic Sugar 이다.
기본적인 자바스크립트의 클래스 정의 및 사용법은 아래와 같다.
/* 클래스 정의 */ class Book { /* 생성자(필드 초기화) */ constructor(title, author, pages) { // 프로퍼티(클래스 변수) this.title = title; this.author = author; this.pages = pages; this.init(); } /* 정적 메서드(수정불가) */ static create(){} /* 인스턴스 메서드 */ init() { console.log(this.title); } } /* 클래스를 통한 객체(인스턴스) 생성 */ let indRevo = new Book('한 권으로 정리하는 4차 산업혁명', '최진기', 367); console.log(indRevo); // Book {} indRevo.init(); // '한 권으로 정리하는 4차 산업혁명'
마찬가지로, 클래스는 객체의 설계도 역할을, 또 이를 통해 새로운 객체(인스턴스)를 생성하는 컨셉에 유념하면서 Typescript와 연계해보자.
- Typescript Class
class Book { public title:string; public author:string; public pages:number = 150; private _manufacturing_plant:string = '충무로 공장'; protected paper_type:string = '밍크지'; constructor(title:string, author:string, pages:number) { this.title = title; this.author = author; this.pages = pages; } /* 메서드 ------------------------------------------------ */ // public 메서드 : 클래스 외부에서 접근 가능 public printPages(): string { return `${this.pages}페이지`; } // protected 메서드 : Book 클래스를 포함한 서브 클래스에서만 접근 가능 protected changePaperType(type:string): void { this.paper_type = type; } // private 메서드 : Book 클래스 내부에서만 접근 가능 private setManufacturingPlant(plant:string): void { this._manufacturing_plant = plant; } /* 클래스 내부 메서드에서 private, protected 메서드 접근 가능 */ public setPlant(plant:string):void { // private 메서드 접근 가능 this.setManufacturingPlant(plant); console.log(this._manufacturing_plant); } } /* 인스턴스 생성 ------------------------------------------------ */ let indRevo = new Book('한 권으로 정리하는 4차 산업혁명', '최진기', 367); console.log(indRevo.printPages()); // '367페이지' console.log(indRevo.changePaperType('인디언지')); // error! changePaperType() -> protected! console.log(indRevo.setManufacturingPlant('파주 공장')); // error! setManufacturingPlant() -> private!
1) Constructor(생성자), Property(매개변수)
마찬가지로, 타입스크립트에서도 constructor() 메서드를 통해 인서턴스의 초기화를 진행할 수 있다.
이 때, 생성자에 적용될 프로퍼티들의 타입을 지정한다. 타입과 함께 접근 제어자를 위 예시와 같이 지정할 수 있다.
2) Access Modifiers(접근 제어자)
프로퍼티의 스코프를 정의하는데 유용한 문법이다. 타입스크립트는 Java와 유사하게 public, private, protected 로 지정한다.
- public : 클래스 외부에서 접근이 가능한 프로퍼티이다. 기본값으로 생략 가능하다.
- private : 클래스 내부에서만 접근이 가능한 프로퍼티이다. Javascript ES6는 #문법을 쓰지만, private가 필드간 고립을 더 잘 보장
- protected : 클래스 내부와, 해당 클래스를 상속받은 클래스 내부에서만 접근 가능하다.
3) Method(메서드)
메서드 역시, 함수 타이핑을 통해 선언을 하면 된다. 메서드 역시, 접근 제어자를 통해 제어범위를 정의할 수 있다.
당연히, private, protected 된 메서드 들일지라도, 클래스 안에서는 참조하여 새로운 메서드를 만들 수 있다.
4) Instance(인스턴스 생성)
마찬가지로, new 키워드를 통해 새로운 객체(인스턴스)를 생성할 수 있다. 이 때, constructor()에서 정의한 프로퍼티들을 입력해줘야 한다.
* constructor 각 매개변수에 '?'를 붙이면 선택적 옵션이 된다. 함수와 마찬가지로, 선택적 프로퍼티는 우측으로 정렬해야한다.
💙 Class 추가문법
- Class 상속
타입스크립트는 Javascript ES6와 마찬가지로 extends 키워드를 통해 클래스 상속을 통한 기능확장이 가능하다.
ES6와 차이점은, 상속된 자식 클래스가 부모 클래스의 프로퍼티를 다른 값으로 덮어쓸 수 있다(Overriding). private 프로퍼티는 불가!
class Book { paper = 'paper'; private spring = true; } class E_Book extends Book { paper = 'screen'; spring = false; // error! private는 수정 불가 } let newBook = new Book() newBook.paper; // "screen"
* super()
일반적으로, 자식 클래스(하위 클래스, 서브 클래스)는 부모 클래스(상위 클래스, 수퍼 클래스)에 부가기능을 갖도록 설계한다.
타입스크립트는, 자식 클래스에서 constructor() 생성자를 사용하여 부모 클래스의 생성자를 덮어쓸 수 있다.
이 때, 반드시 super() 메서드를 통해 부모 클래스에서 요구되는 인자를 전달해야 한다.
class Animal { name: string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters); } } class Horse extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 45) { console.log("Galloping..."); super.move(distanceInMeters); } } let sam = new Snake("Sammy the Python"); let tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
Animal 클래스에서 상속된 Snake, Horse 클래스가 있다. 이들은, constructor() 에서 name 프로퍼티를 정의해야 한다.
이는, 부모 클래스인 Animal의 constructor() 에서 정의한 프로퍼티므로, super(name)을 통해 이를 가져와야 한다.
- Accessors(접근자) : Getter & Setter
만약 프로퍼티에 제약조건을 적용한다고 가정하자. 이 때, Setter를 통해 인스턴스 초기화 전에 조건을 걸 수 있다.
또, private 프로퍼티에 접근해야 하는 경우, Getter 함수를 통해 이를 연동해야 한다
class Employee { private _fullName: string; // Getter : private 프로퍼티 접근 get fullName(): string { return this._fullName; } // Setter : name 길이가 10 미만인 경우만, _fullName 프로퍼티에 할당 set fullName(newName: string) { if (newName && newName.length > 10) { throw new Error("fullName has a max length of ten"); } this._fullName = newName; } } let employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { console.log(employee.fullName); }
* _[변수명] 은, private 프로퍼티를 정의하는 컨벤션으로 '_'(언더스코어)를 앞에 붙여준다.
- Static Property, Method
인스턴스 생성 없이, 클래스의 프로퍼티 또는 메서드를 사용하고자 할 때 static 키워드로 프로퍼티, 메서드를 정의한다.
class Mathmatics { // 스태틱 속성 static PI:number = Math.PI; // 스태틱 메서드 // circumference = 둘레(원주) static calcCircumference(radius:number) :number { return this.PI * radius * 2; } static calcCircleWidth(radius:number): number { return this.PI * Math.pow(radius, 2); } } // radius = 반지름 let radius = 4; console.log('PI(원주율) = ', Mathmatics.PI); console.log(`반지름이 ${radius}인 원의 넓이: πr² = `, Mathmatics.calcCircleWidth(radius)); console.log(`반지름이 ${radius}인 원의 둘레: 2πr = `, Mathmatics.calcCircumference(radius));
* 정적 요소는 인스턴스(객체)가 아닌 클래스 자체를 참조한다. 그렇기에, 인스턴스화가 생략되며, this가 아닌 클래스명 자체에 접근한다.
- Abstract Classes(추상 클래스)
추상 클래스는 다른 클래스들이 파생될 수 있는 기초 클래스다. 참조만을 위한 클래스이며, 직접 인스턴스화는 불가능하다.
abstract 키워드를 통해 추상 클래스를 정의하며, 이는 추상 클래스 안에 추상 메서드를 정의할 때도 사용된다.
abstract class Project { public project_name:string|null = null; private budget:number = 2000000000; // 예산 // 추상 메서드 정의 public abstract changeProjectName(name:string): void; // 실제 메서드 정의 public calcBudget(): number { return this.budget * 2; } }
추상 메서드는 구현을 포함하지 않으나, 파생된 클래스에선 반드시 구현되어야 한다. 구현하지 않으면 오류가 발생한다.
// 클래스 ⟸ 추상 클래스 상속 class WebProject extends Project { // error! 추상 멤버 'changeProjectName'을(를) 구현하지 않습니다 }
- 인터페이스로서 클래스 사용하기
클래스는 하나의 객체이자, 내부에 변수(프로퍼티)들의 값과 타입을 지정할 수 있다.
그렇기 때문에, 클래스는 인터페이스와 연동되어 사용될 수 있다.
class Point { x: number; y: number; } interface Point3d extends Point { z: number; } let point3d: Point3d = {x: 1, y: 2, z: 3};
* Class vs Interface
앞서, 나는 클래스를 설계도에 비유했다. 객체가 가지는 프로퍼티나 메서드에 대한 구성을 클래스는 담고 있다.
인터페이스도 객체의 프로퍼티나 메서드에 대한 정보를 담고 있다. 다만, 이는 기능보다 타입에 중점을 둔다는 차이점이 있다.
또한, 인터페이스는 constructor() 를 통한 초기화나 인스턴스 구현이 불가능하다. 이러한 목적의 차이에 따라 둘을 잘 구분해서 사용해야겠다.
Typescript의 클래스를 시작하기에 앞서, 자바스크립트의 안습감이 기억나서 많이 긴장을 하고 접근했다.
하지만, 오히려 자바스크립트 클래스를 어느정도 이해한 상태에서 공부했기에 수월하게 이해할 수 있었다.
막상은, 기존의 문법에서 큰 차이는 없으며 타입지정하는 방법을 예제들을 통해 경험하면서 감을 익혀갔다.
[출처]
- Typescript 핸드북 : typescript-kr.github.io/pages/generics.html
- Typescript 가이드북 : yamoo9.gitbook.io/typescript/generics/type-inheritance
반응형