TIL - Typescript 2

김수지·2020년 3월 17일
1

TILs

목록 보기
30/39

Today What I Learned

Javascript를 배우고 있습니다. 매일 배운 것을 이해한만큼 정리해봅니다.
오늘은 Typescript를 정리 해봅니다.


지난 타입스크립트 TIL 기본 개념에 이어서 클래스 선언 시 public/private/protected, Interface 등의 개념을 정리해본다. JS만 하다가 찐OOP 용어들을 만나니 신기하면서 당황스럽다 하하.

1. Class in Typescript

  • 타입스크립트에서도 클래스 선언이 당연히 가능하다. 자바스크립트의 ES2015부터 사용가능한 Class 문법과 살짝 다른 점이 있다.
  • 자바스크립트의 Class 문법에서는 생성자(contructor) 내부에서 클래스 속성값들을 선언할 수 있다. 그러나 타입스크립트에서는 생성자에서 선언되는 속성값에 대한 타입을 먼저 선언해주어야 한다. 그래서 생성자 함수에 앞서서 사용될 속성값에 대한 타입 선언을 하는 형태이다.
// 클래스에서 타입 선언 예시
class Greeter {
  greeting: string; // 먼저 타입 선언
  
  constructor(message: string) {
    this.greeting = message; // 생성자 함수 안에서 속성 값 할당
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world");
  • 이렇게 선언된 클래스는 oop의 특징과 같이 상속할 수 있다.
class Animal {
  name: string; 
  constructor(theName: string) {
    this.name = theName;
  }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}
// 상속 1
class Snake extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 5) {
    console.log("Slithering...");
    super.move(distanceInMeters);
  }
}
// 상속 2
class Horse extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 45) {
    console.log("Galloping...");
    super.move(distanceInMeters);
  }
}
// instances
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move(); // default값 5(number type) 반영
// Slithering...
// Sammy the Python moved 5m.
tom.move(34);
// Galloping...
// Tommy the Palomino moved 34m.

1. public, private, protected modifiers

  • 객체지향에서는 클래스 내부의 프로퍼티와 메소드에 modifiers를 적용할 수 있는데, 우리나라 말로는 접근 제한자라고 한다.
  • 종류: public, private, protected
  • 각 각 성격에 따라 선언 후 사용자가 접근할 수 있는 범위가 다르다.
    출처: https://poiemaweb.com/typescript-class
  • 타입스크립트에서는 기본적으로 접근 제한자를 설정하지 않을 때 public으로 선언되었다고 본다. 따라서 public으로 선언할 프로퍼티/메소드의 경우는 별도로 public이라고 적지 않아도 된다.
  • public : public으로 선언된 프로퍼티/메소드는 본인 클래스 내부와 자식 클래스 내부, 그리고 인스턴트화 된 상태에서도 접근이 가능하다. 가장 익숙한 형태인 듯. (나는 js만 경험했지만 다른 oop에서 default modifier는 private이라고 한다.)
class Animal {
  public name: string; // default로 public이므로 삭제 가능
  public constructor(theName: string) { // default로 public이므로 삭제 가능
    this.name = theName; 
  }
  public move(distanceInMeters: number) { // default로 public이므로 삭제 가능
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}
  • private : private은 가장 접근이 어려운 형태의 제한자로 본인 클래스 내부에서만 호출 가능하다.
class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

new Animal("Cat").name; // Error: 'name' is private;
  • protected : protected는 본인 클래스와 자식 클래스 내부에서만 접근 가능하다. 대신 인스턴스화 된 객체에서는 접근이 되지 않아 호출 할 때 에러가 발생한다.
class Person {
  protected name: string;
  protected constructor(theName: string) {
    this.name = theName;
  }
}

// Person 상속 받기
class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}
// instance화
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected

2. read only modifier

  • read only : read only를 사용하면 프로퍼티 선언 시나 생성자 내부에서만 해당 변수가 할당이 가능하고 그 후로는 재할당이 되지 않는다. 정보를 은닉하는 여러 과정에서 케이스에 따라 사용할 수 있을 것으로 보인다.
class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;
  constructor(theName: string) {
    this.name = theName;
  }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.

3. accessor : getter & setter

  • 타입스크립트에서는 클래스에서 getter와 setter 개념도 사용할 수 있는데 이를 accessor라고 한다.
  • getter를 통해 클래스의 프로퍼티를 가져오면서 setter에서 별도 조건을 붙여 확인하고자 하는 것들을 셋팅할 수 있다.
const fullNameMaxLength = 10;

class Employee {
  private _fullName: string;

  get fullName(): string {
    return this._fullName;
  }

  set fullName(newName: string) {
    if (newName && newName.length > fullNameMaxLength) {
      throw new Error("fullName has a max length of " + fullNameMaxLength);
    }

    this._fullName = newName;
  }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
  console.log(employee.fullName);
}

4. static & abstract

  • static을 이용해서 클래스 내부에 선언된 프로퍼티/메소드에서 static 키워드를 사용할 수 있다.
  • static 키워드를 사용하는 경우는 클래스 원형에서 참조하기 때문에 인스턴스화 된 객체에서 프로퍼티나 메소드를 호출하는 경우에는 원하는 값을 얻을 수 없고 에러가 발생한다.
// static method 예시
class Foo {
  constructor(prop) {
    this.prop = prop;
  }

  static staticMethod() {
    return 'staticMethod';
  }

  prototypeMethod() {
    return this.prop;
  }
}

console.log(Foo.staticMethod()); //staticMethod
const foo = new Foo(123);
console.log(foo.staticMethod()); // Uncaught TypeError: foo.staticMethod is not a function
// static property 예시
class Foo {
  static instanceCounter = 0;
  constructor() {
    Foo.instanceCounter++;
  }
}

var foo1 = new Foo();
var foo2 = new Foo();

console.log(Foo.instanceCounter);  // 2
console.log(foo2.instanceCounter); // error TS2339: Property 'instanceCounter' does not exist on type 'Foo'.
  • abstract: abstract method(추상 메소드)는 내용이 없이 메소드 이름과 타입만이 선언된 메소드이다. abstract class는 이런 추상 메소드를 1개 이상 가지고 있는 클래스를 가리킨다.
abstract class Animal {
  abstract makeSound(): void;
  move(): void {
    console.log("roaming the earth...");
  }
}

2. Interface in Typescript

  • interface 또한 타입스크립트에서 만난 새로운 개념이다(아.. 찐oop들이여) 자바스크립트에서는 지원하지 않지만 타입스크립트에서는 지원하는 개념이다.
  • Interface는 class처럼 프로퍼티와 메소드로 구성되어 있지만, class와 달리 인스턴스를 생성할 수 없고, 메소드 또한 모두 abstract method이다. abstract class와 비슷해 보이지만 그렇다고 굳이 선언 시 abstract이라는 단어는 쓰지 않는다.

1. 그럼 interface는 왜, 어디다 쓰는 것일까?

  • 일단 interface를 사용하면 프로퍼티/메소드의 형태를 일관성 있게 유지할 수 있다.
  • interface를 선언한 상태에서 interface 자체를 타입으로 쓸 수 있다. 그 전까지는 string, number, array, object 등을 타입으로 지정했다면 이것보다 다채로운 타입 모양을 interface로 정의한 다음 그 모양 그대로 타입을 쓸 수 있다.
interface Todo {
  id: number;
  content: string;
  completed: boolean;
}
let todo: Todo;
todo = { id: 1, content: 'typescript', completed: false };// interface 모양을 그대로 따라 해야만 할당이 가능
  • 인터페이스를 타입으로 활용할 때 인터페이스 내 선언된 모든 프로퍼티를 사용하지 않고 일부만을 사용할 수 있다. 이럴 때는 선택적 프로퍼티를 가리키는 ?를 넣어줄 수 있다.
interface SquareConfig {
  color?: string;
  width?: number;
}

2. interface로 지정할 수 있는 타입들

  • Function type: 인터페이스로 함수 자체를 선언할 수도 있다.
interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
  let result = src.search(sub);
  return result > -1;
};
  • Indexible type: 인덱스를 활용하는 타입으로 인터페이스를 지정할 수도 있다.
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];
  • Class type: 인터페이스를 클래스형으로 정의하고 클래스에 적용할 수 있다.
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(h: number, m: number) {}
}
profile
선한 변화와 사회적 가치를 만들고 싶은 체인지 메이커+개발자입니다.

2개의 댓글

comment-user-thumbnail
2020년 3월 19일

좋은 글 잘 봤어요! 근데 중간에 protected랑 private이랑 설명이 반대로 되어있는 것 같아요!

1개의 답글