리터럴 타입은 구체적인 타입을 얘기한다.
예를 들어 타입을 hello
라는 타입을 새로 정의하면 이것을 대상타입으로 갖는 변수에는 꼭 hello
라는 문자열만 할당될 수 있다.
const helloWorld = "Hello World";
let hiWorld = "Hi World";
const
는 변하지 않으므로 helloWorld
의 타입은 "Hello World"
가 된다.
반면 let
은 변할 수 있으므로 hiWorld
의 타입은 string
이 된다.
유니언 타입은 여러 타입 중 하나가 될 수 있는 값을 의미한다.
세로 막대 (|
)로 각 타입을 구분하여, number | string | boolean
은 값의 타입이 number
, string
혹은 boolean
이 될 수 있음을 의미한다.
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
declare function getSmallPet(): Fish | Bird;
let pet = getSmallPet();
pet.layEggs();
pet.swim();
두 타입이 동시에 가진 멤버인 layEggs
는 확실히 가진 것이므로 호출이 가능하다.
하지만 swim
은 확신할 수 없는 멤버이므로 호출할 수 없다.
교차 타입은 여러 타입을 하나로 결합한다.
기존 타입을 합쳐 필요한 기능을 모두 가진 단일 타입을 얻을 수 있다.
결합을 하기 위해선 인터페이스로 규격해야한다.
type
은 결합이 불가능하다.
기존 JavaScript는 재사용할 수 있는 컴포넌트를 만들기 위해 함수와 프로토타입-기반 상속을 사용했지만, 객체 지향 접근 방식에 익숙한 프로그래머의 입장에서는 클래스가 함수를 상속받고 이런 클래스에서 객체가 만들어지는 것에 다소 어색함을 느꼈다.
ECMAScript 2015를 시작으로 JavaScript 프로그래머들은 이런 객체-지향적 클래스-기반의 접근 방식을 사용해서 애플리케이션을 만들 수 있게 되었다.
파생된 클래스의 생성자 함수는 기초 클래스의 생성자를 실행하려면 super()
를 호출해야 한다.
또는 생성자 내의 this에 있는 프로퍼티에 접근하려면 그 전에 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);
}
}
상속된 메서드와 동일한 메서드가 정의되면 새로 정의된 메서드를 따른다.
위에서는 Snake
인스턴스는 move
를 오버라이드 해서 각각 클래스의 특성에 맞게 기능을 가진 move
를 재생산했다.
멤버에 접근하지 못하도록 하려면 멤버를 private
으로 표시하는 방법을 사용할 수 있다.
타입스크립트는 private
및 protected
멤버가 있는 타입들을 비교할 때는 타입을 다르게 처리한다.
호환된다고 판단되는 두 개의 타입 중 한 쪽에서 private
멤버를 가지고 있다면, 다른 한 쪽도 무조건 동일한 선언에 private
멤버를 가지고 있어야 하며 protected
멤버도 똑같이 적용된다.
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
animal = employee; // 오류: 'Animal'과 'Employee'은 호환될 수 없음.
Rhino
는 Animal
을 상속하여 super()
를 호출했기 때문에 동일한 멤버를 가지고 잇어 호환이 가능하다.
하지만 Employee
는 전혀 다른곳에서 선언을 한 멤버이기 때문에 호환이 불가능하고 오류가 발생한다.
readonly
키워드를 작성해서 프로퍼티를 오직 읽을 수만 있도록 지정할 수 있다.
다른 클래스들이 파생될 수 있는 기초클래스로 그 자체만으로는 인스턴스화할 수 없다.
인터페이스와 차이점은 멤버에 대한 구현 세부 정보를 포함할 수 있다는 것이다.
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("roaming the earth...");
}
추상 클래스 내의 메서드를 abstract
를 사용하여 추상메서드로 정의할 수 있다.
추상메서드는 구현 정보를 포함하지 않고 파생된 클래스에서 구현하게 된다.