클래스는 객체의 청사진으로 데이터를 저장하고 메서드를 실행하기 위해 메서드를 저장하는데 사용하는 데이터 구조이다. 이를 이용하여 객체의 형태, 데이터, 메소드를 정의할 수 있으며 동일한 구조 및 기능을 하는 객체들을 재사용 및 복사할 수 있다.
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
}
클래스는 첫글자를 대문자로 작성하며 생성자 메서드(예약어)를 포함한다. 생성자를 통해 객체를 초기화하며 클래스 및 객체에 연결된다.
const accounting = new Department('Accounting');
console.log(accounting);
// Department {name:"Accounting"}
클래스 속성과 메소드를 참조하기 위해 this를 사용한다. 이는 자바스크립트에서 배웠던 this 사용을 참고하자.
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
discribe(this: Department) {
// discribe 코드의 this는 Department 클래스에 기반한 인스턴스를 참조해야 한다
console.log('Department' + this.name);
}
}
const accounting = new Department('Accounting');
// Department { name: "Accounting" }
accounting.discribe();
const accountingCopy = { name: 'DUMMY', describe: accounting.discribe };
accountingCopy.discribe();
클래스는 하나의 방법으로만 사용하며 클래스 외부에서의 다른 접근을 막아야 하므로 private(생성된 객체 내부에서만 접근)를 사용한다.
또한 private키워드가 붙은 프로퍼티는 _(언더바)를 붙이는것이 통상적이라고 한다. 후에 외부에서 접근할때는 get / set를 이용한다.
class Department {
private employees: string[] = [];
addEmployee(employee: string) {
this.employees.push(employee);
}
}
public은 디폴트 값이며 별도로 메서드에 추가하지 않아도 된다. 단, 명시적으로 다음과 같이 수정할 수 있다.
class Input {
public name: string;
public constructor(name: string) {
this.name = name;
}
public inputName() {
console.log(`input name is ${ this.name }`);
}
}
class Button extends Input {
public constructor(name: string) {
super(name);
}
public inputName() {
console.log(`button name is ${ this.name }`);
}
}
protected는 선언한 클래스를 포함하여 상속받는 하위클래스에서만 접근 가능.
생성자 함수 내에 이중초기화 코드를 입력하고 필드 정의를 제거함으로써 필드를 찾은 다음 값을 저장해야 하는 이중 초기화 코드를 한 번에 처리하도록 축약한다.
//기존 코드
class Department {
private id: string;
private name: string;
private employees: string[] = [];
constructor(n: string) {
}
}
// 약식 초기화 코드
class Department {
private employees: string[] = [];
constructor(private id: string, public name: string) {
// public을 추가 작성하는 이유는 동일한 이름으로 속성을 만들고 싶다는 타입스크립트에게 알리는 명시적인 명령
// n을 name으로 바꾸어 name 속성이 생성된 클래스에 생성될 수 있도록 해야 한다.
}
}
private이나 public이어서도 안 되고 초기화 후에 변경되어서도 안 되는 특정 필드가 있는 경우 확실히 변경되지 않게 하려면 readonly를 추가한다.
constructor(private readonly id: string, public name: string)
ES6 문법에서 사용하는 extends 키워드를 사용한다. 생성자를 포함 부모클래스가 가진것을 상속한다.
class ITDepartment extends Department {
constructor(id: string, admins: string[]) {
super(id, 'IT');
// super로 기본(부모) 클래스의 생성자를 호출한다.
this.admins = admins;
}
}
상위 클래스에서 정의한 메서드를 자식클래스에서 재정의 할 수 있다.
private : 정의된 클래스 내에서만 접근 가능하며 해당 클래스로부터 상속받는 클래스에서는 접근 불가능한 속성
protected : 상속받는 클래스에서 접근할 수 있지만 외부에서는 변경 불가능한 속성
class AccountingDepartment extends Department {
constructor(id:string, private reportes:string[]){
super(id, 'Accounting');
}
addEmployee(name:string){
this.employees.push(name)
}
}
로직을 캡슐화 하고 속성을 읽거나 설정하려할 때 실행되어야 하는 추가적인 로직을 추가할때 유용하다.
게터
get 키워드를 사용하여 값을 가지고 올때, 함수나 메소드를 실행하는 속성으로 private로 지정된 값에 접근할 수 있고 반드시 무언가를 반환해야하기 때문에 return 문 필요
class AccountingDepartment extends Department {
private lastReport:string;
get mostRecentREport(){
if(this.lastReport){
return this.lastReport;
}
throw new Error('No report found.')
}
constructor(id:string, private reportes:string[]){
super(id, 'Accounting');
this.lastReport = reports[0]
}
...
}
const accounting = new AccountingDepartment('d2',[]);
console.log(accounting.mostRecentReport)
세터
set 키워드를 사용하여 값을 설정하고 추가할때 사용하는 속성
class AccountingDepartment extends Department {
private lastReport:string;
get mostRecentReport(){
if(this.lastReport){
return this.lastReport;
}
throw new Error('No report found.')
}
set mostRecentReport(value:string){
if(!value){
throw new Error('Please pass in a valid value!')
}
this.addReport(value)
}
...
}
const accounting = new AccountingDepartment('d2',[]);
accounting.mostRecentReport = '';
정적 메서드와 속성을 사용해서 클래스의 인스턴스에서 접근할 수 없는 속성 및 메소드를 클래스에 추가할 수 있다
새 키워드 없이 직접 클래스에서 호출하며 그룹화 하거나 클래스에 매핑하려는 유틸리티 함수 혹은 클래스에 저장하고자 하는 전역상수에 사용한다.
static 키워드를 붙여 정적 메소드를 만들고 인스턴스 생성 없이도 호출할 수 있다.
class Student {
constructor(protected name: string) {}
static printInfo(): void {
console.log(`이름: ${this.name}`);
}
}
메서드의 형태와 구조를 정의하지만 body가 구현되어 있지 않은 클래스
추상 메서드는 abstract키워드를 붙여 사용하며 추상 클래스에서만 사용할 수 있기 때문에 추상 클래스를 상속받은 클래스는 클래스내에 추상 속성이 필요하다. 또한 추상 클래스로는 객체 인스턴스를 생성할 수 없고 상속용으로만 가능하다.
abstract describe(this:Department) : void;
@ 클래스와 인스턴스의 차이?
인터페이스는 모든 메서드가 추상 메소드이며 추상 클래스는 추상 메소서드만 포함하는 것이 아니라 실제 구현이 있는 메서드도 포함할 수 있다.
특정 클래스의 인스턴스를 정확히 하나만 갖도혹 하며 정적 메소드나 속성을 사용할 수 없거나 사용하지 않고자 하는 동시에 항상 클래스를 기반으로 정확히 하나의 객체만 가지도록 하는 경우에 사용
객체의 타입을 정의할 수 있는 interface키워드이며 프로퍼티와 메소드를 가질 수 있다는 점에서 클래스와 유사하지만 직접 인스턴스를 생성할 수 없는 추상메소드이다.
인터페이스 선언
interface 인터페이스 이름{
property name[?]: property type[,...]
}
객체의 타입을 정의하는 것이 목적이기 때문에 {}로 프로퍼티 이름과 타입을 나열하는 형태로 사용
interface Person {
name:string;
age:number;
greet(phrase:string):void;
}
let user1 : Person;
user1 = {
name:'ABC',
age:20,
greet(phrase:string){
console.log(phrase)
}
}
인터페이스를 정의할때는 객체의 구조를 정의하는데 사용한다.
또한 아래의 예시는 서로 다른 클래스의 기능을 공유하기 위해 사용되는데 추상클래스의 구현부 내용과 내가 작성한 부분을 혼동하지 않도록 주의한다.
interface Greetable {
name: string;
greet(phrase: string): void;
}
class Person implements Greetable {
name: string;
age = 30;
constructor(n: string) {
this.name = n;
}
greet() {
console.log(phrase + ' ' + this.name);
}
}
인터페이스 내에 readonly는 사용 가능하나 public, private는 사용할 수 없다.
interface Named {
readonly name: string;
}
interface Greetable extends Named {
greet(phrase: string): void;
}
extends를 사용하여 둘 이상의 인터페이스를 하나로 병합(확장)할 수 있다.
인터페이스는 함수의 타입으로도 사용할 수 있으며 타입이 선언된 파라미터의 리스트와 리턴타입을 정의하고 함수는 이를 준수해야한다.
type AddFn = (a:number, b:number)=>number;
interface AddFn {
(a:number, b:number):number;
}
인터페이스로 정의한 함수를 사용할 때는 익명함수를 사용한다.
let add : AddFn;
add = (a:number, b:number) => {
return a+b;
}
속성 뒤에 '?'를 추가하여 선택적 매개변수와 속성을 만들 수 있다
interface Named {
name:string;
outputName?:string;
}
인터페이스에서는 선택적 속성을 입력하고 클래스에서는 선택적이지 않은 속성을 입력했다면 로직이 항상 초기화되도록 해야한다.
interface Named {
name?:string;
}
class Person implements Named{
name:string;
constructor(n:string){
this.name = n;
if(n){
this.name = n;
}
}
}