TypeScript Study Note

Ginie·2021년 1월 19일
0

TypeScript

목록 보기
9/11
post-thumbnail

클래스

class Greeter { // 새로운 클래스 생성
    greeting: string; // 프로퍼티
    constructor(message: string) { // 생성지
        this.greeting = message; // 클래스 멤버를 참조할 때 this로 멤버에 접근
    }
    greet() { // 매서드
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter('world'); // 클래스 인스턴스 생성 이전에 정의한 생성자 호출

상속

  • 상속을 이용해 이미 존재하는 클래스를 확장해 새로운 클래스를 만들 수 있다.
class Animal { 
// 기초 클래스 = 상위 클래스(superclasses)
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

class Dog extends Animal { 
// 기초 클래스에서 파생된 하위 클래스(subclasses)
    bark() {
        console.log('Woof! Woof!')
    }
}

const dog = new Dog(); 
// 👆 상위 클래스를 확장하기 때문에 bark()와 move() 둘 다 가진 Dog 인스턴스를 생성할 수 있다.

dog.bark();
dog.move(10);
dog.bark();
  • 더 복잡한 예제 확인하기 👇 Playground
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 { // Animal 하위 클래스 
    constructor(name: string) { super(name); } // 상위 클래스의 생성자를 실행할 super()를 호출 ⭐
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class Horse extends Animal { // Animal 하위 클래스
    constructor(name: string) { super(name); } // 상위 클래스의 생성자를 실행할 super()를 호출 ⭐
    move(distanceInMeters = 45) {
        console.log('Galloping...');
        super.move(distanceInMeters);
    }
}

let sam = new Snake('Sammy the Python');
let tom: Animal = new Horse('Tommy the Palomino'); // tom은 Animal로 선언 되었지만, Horse의 값을 가지므로 

sam.move();
tom.move(34); // Horse의 오버라이딩 메서드를 호출한다. 

결과 👇

  • 상위 클래스 생성자 내에서 this에 있는 프로퍼티name에 접근하기 전에 super()를 호출해야 한다는 점!
  • 상위 클래스 Animalmove를 오버라이드해서 각각 클래스의 특성에 맞는 기능을 가진 move를 생성한다.
잠깐 주목!

🤷‍♀️ 나는 오버라이딩이 뭔지 몰라요!
👩‍🏫 Override는 '기각하다 ','무시하다 '라는 뜻을 갖고 있어. 즉 오버라이드는 기존의 것을 무시하고 덮어쓴다는 의미야.
🙋‍♀️ 아~ 그러면 상위 클래스가 가지고 있는 메서드를 하위 클래스에서 동일한 메서드로 덮어쓰기 한다는 의미인가요?
👩‍🏫 맞아~ 여기서 "동일한 메서드"란 이름도 같고, 반환형태도 같고, 매개변수의 개수랑 타입까지 모두 같음을 의미해

Public, Private, Protected 지정자

Public

  • C#에서 멤버를 노출 시킬 때 public을 붙인다.
  • TS에서는 기본적으로 각 멤버는 public이다.
    이전 예제에 public을 붙여보자.
class Animal {
    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

ECMAScript 비공개 필드

ECMA스크립트(ECMAScript, 또는 ES)란, Ecma International이 ECMA-262 기술 규격에 따라 정의하고 있는 표준화된 스크립트 프로그래밍 언어를 말한다. 자바스크립트를 표준화하기 위해 만들어졌다. 액션스크립트와 J스크립트 등 다른 구현체도 포함하고 있다. ECMA스크립트는 웹의 클라이언트 사이드 스크립트로 많이 사용되며 Node.js를 사용한 서버 응용 프로그램 및 서비스에도 점차 많이 쓰이고 있다.

  • JS의 새로운 문법을 지원한다.
  • 프로퍼티 #name은 비공개 식별자이기 때문에 Animal 클래스 외부에서 접근 할 수 없다.

TypeScript의 private 이해하기

  • private 은 클래스 외부에서 멤버에 접근하지 못하게 한다.
class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}


new Animal("Cat").name; // 오류 : name은 비공개로 선언 되어 있습니다.
  • TS는 두개의 다른 타입을 비교할 때 어디서 왔던지 상관 하지 않고, 모든 멤버의 타입이 호환 된다면, 그 타입들 자체가 호환 가능하다고 생각한다.
    • 그러나 private 및 protected 멤버가 있는 타입들을 비교할 때는 타입을 다르게 처리한다.
    • 호환된다고 판단되는 두 개의 타입 둘 다 private을 가지고 있어야 한다. 이것은 protected도 마찬가지!
class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}


class Rhino extends Animal { // 하위 클래스 
    constructor() { super('Rhino'); } // private 부분을 공유함 Animal과 호환 가능
}

class Employee { // Animal 과 같은 형태
    private name: string; // Animal에서 선언한게 아니여서 호환 불가능
    constructor(theName: string) { this.name = theName; }
}

let animal = new Animal('Gort');
let rhino = new Rhino();
let employee = new Employee('Bob');

animal = rhino;
animal = employee; // 오류 호환 될 수 없음

protected 이해하기

  • protected로 선언된 멤버를 파생된 클래스 내에서 접근 할 수 있다.
class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
} // 여기 외부에서 name을 사용할 수 없지만

class Employee extends Person { // 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}.`
    }
}

let howard = new Employee('Howard', 'Sales');
console.log(howard.getElevatorPitch());
console.log(name); // 오류
  • protected 는 생성자로도 표시될 수 있다.
class Person {
    protected name: string;
    protected constructor(theName: string) { this.name = theName; }
    // 클래스를 포함하는 클래스 외부에서 인스턴스화 할 수 없지만 확장 할 수 있다.
}

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}.`
    }
}

let howard = new Employee('Howard', 'Sales');
console.log(howard.getElevatorPitch());

읽기 전용 지정자

  • readonly 키워드를 사용해 프로퍼티를 읽기 전용으로 만들 수 있다. 그리고 그 프로퍼티는 생성자에서 초기화 한다.
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'; // 오류 name은 읽기 전용!

매개변수 프로퍼티

  • 한 곳에서 멤버를 만들고 초기화할 수 있다.
  • 매개변수 프로퍼티는 접근 지정자, readonly, 혹은 둘 다 생성자 매개변수에 앞에 붙여 선언한다.
    -매개변수 프로퍼티에 private을 사용하면 비공개 멤버를 선언하고 초기화한다. (public, protected, readonly도 동일)
class Octopus {
    readonly numberOfLegs: number = 8;
    constructor(readonly name: string) { // 파라미터를 사용해 theName을 지우고 name멤버를 생성하고 초기화 했다. 선언과 할당을 한 곳으로 함.
    }
}

접근자

  • 객체 멤버에 접근하는 방법을 세밀하게 제어할 수 있다.
class Employee {
    fullName: string;
}

let employee = new Employee();
employee.fullName = 'Bob Smith';

if (employee.fullName) {
    console.log(employee.fullName);
}

여기서 getters, setter를 추가해보자.

const fullNameMaxLength = 10;

class Employee {
    private _fullName: string;

    get fullName(): string { // 기존 기능 유지를 위해 fullName을 수정하지 않는 getter 추가
        return this._fullName;
    }

    set fullName(newName: string) { // newName의 길이를 확인한다.
        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);
}

주의사항

ECMAScript 5 이상을 출력하도록 컴파일러를 설정 (ECMAScript 3으로의 하향 조정은 지원되지 않음.)
getset이 없는 접근자는 자동으로 readonly로 유추된다. (프로퍼티 내의 사용자들이 변경할 수 없다. -> .d.ts 파일을 생성할 때 유용하다.)

전역 프로퍼티

  • 클래스 자체에서 보이는 전역 멤버를 생성할 수 있다.
class Grid {
    static origin = { x: 0, y: 0 }; // 모든 grid의 일반적인 값이기 때문에 static 사용
    calculateDistanceFromOrigin(point: { x: number; y: number; }) {
        let xDist = (point.x - Grid.origin.x);         
        let yDist = (point.y - Grid.origin.y);
      // 각 인스턴스는 클래스 이름을 앞에 붙여 접근 가능, 인스턴스에 this를 붙여 접근하는 것과 비슷하다.
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor(public scale: number) { }
}

let grid1 = new Grid(1.0);
let grid2 = new Grid(5.0);

console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));
console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));

추상 클래스

  • 다른 클래스들이 파생될 수 있는 기초 클래스.
  • 직접 인스턴화할 수 없다.
  • 인터페이스와 달리 멤버에 대한 구현 세부 정보를 포함할 수 있다.
  • abstract("추상적인" 이라는 뜻) 키워드는 추상클래스 뿐만 아니라 추상 메서드를 정의 하는데 사용한다.
abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log('roaming the earth...');
    }
}
  • 추상 클래스 내에서 추상으로 표시된 메서드는 구현을 포함하지 않으며 반드시 파생된 클래스에서 구현.
  • 메서드 본문을 포함하지 않고 메서드를 정의(interface와 비슷하다)
  • 선택적으로 접근 지정자를 포함할 수 있다.
abstract class Department {
    constructor(public name: string) {
    }
    printName(): void {
        console.log('Department name: ' + this.name);
    }
    abstract printMeeting(): void; // 반드시 파생된 클래스에서 구현.
}

class AccountingDepartment extends Department {
    constructor() {
        super('Acconting and Auditing'); // 파생된 클래스의 생성자는 반드시 super()을 호출 해야한다.
    }
    printMeeting(): void {
        console.log("The Accounting Department meets each Monday at 10am.");
    }
    generateReports(): void {
        console.log("Generating accounting reports...");
    }
}
let department: Department //  추상 타입의 레퍼런스를 생성.
department = new Department(); // 오류: 추상 클래스는 인스턴스를 만들 수 없음.
department = new AccountingDepartment(); // 추상이 아닌 하위 클래스를 생성하고 할당한다.
department.printName();
department.printMeeting();
department.generateReports(); // 오류 : 'Department' 형식에 'generateReports' 속성이 없다.

고급 기법

생성자 함수

  • 클래스의 인스턴스 타입
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return 'Hello, ' + this.greeting;
    }
}
let greeter: Greeter; // Greeter 클래스의 인스턴스 타입으로 Greeter를 사용
greeter = new Greeter("world"); // 생성자 함수라고 불리는 또 다른 값을 생성
console.log(greeter.greet()); // "Hello, world"
  • 생성자 함수는 클래스의 모든 전역 변수들을 포함한다.
  • 또 다른 방법은 인스턴스 측면과 정적 측면이 있다는 것이다.
class Greeter {
    static standardGreeting = 'Hello, there'
    greeting: string;
    greet() {
        if (this.greeting) {
            return 'Hello, ' + this.greeting
        } else {
            return Greeter.standardGreeting;
        }
    }
}

let greeter1: Greeter; // 이전 예제와 비슷하게 작동
greeter1 = new Greeter(); // 인스턴화 하고 이 객체를 사용한다.
console.log(greeter1.greet());

let greeterMaker: typeof Greeter = Greeter; //  클래스를 직접 사용한다. 클래스 자체를 유지하거나 생성자 함수를 다르게 설명한다.
// typeof Greeter를 사용하여 Greeter라는 심볼의 타입을 제공한다. 
// Greeter 클래스의 인스턴스를 만드는 생성자와 함께 Greeter의 모든 정적 멤버를 포함할 것이다.
greeterMaker.standardGreeting = 'Hey, there!';

let greeter2: Greeter = new greeterMaker(); // Greeter의 새로운 인스턴스를 생성하고 이전과 같이 호출한다.
console.log(greeter2.greet())

인터페이스로써 클래스 사용하기

  • 클래스는 타입을 생성하기 때문에 인터페이스를 사용할 수 있는 동일한 위치에서 사용할 수 있다.
class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};
profile
느리지만 꾸준한 프론트엔드 주니어 개발자 🐢

0개의 댓글