티스토리 뷰

do/typescript

typescript 4

dooo.park 2019. 4. 14. 19:06

인터페이스(interface)

인터페이스를 통해 값이 따라야 할 제약을 타입으로 표현 한다.

값이 어떤 멤버를 가져야 하고 각 멤버의 타입은 어때야 하는지 서술할 수 있다.

interface x {
  a: string;
  b: number;
}

인터페이스의 속성을 읽기 전용 또는 선택 속성으로 정의할 수 있다.

interface x {
  a: string;
  readonly b: number; //읽기전용
  c?: string; //선택
}
const y: x = { a: 'ab', b: 11 };
y.b = 22; // error TS2540: Cannot assign to 'b' because it is a constant or a read-only property.

함수 인터페이스

인터페이스를 이용해 함수 타입을 표현할 수 있다. 

함수 타입의 표현을 위해 호출 시그니쳐(call signature)를 제공해야 하는데, 함수 타입 정의와 유사한 아래 문법을 사용한다.

(매개변수1 이름: 매개변수1 타입, 매개변수2 이름: 매개변수2 타입, ...): 반환 타입

예를 들어, User 타입의 값 user를 받아 이름을 반환하는 함수 인터페이스를 다음과 같이 적을 수 있다.

interface GetUserName {
  (user: User): string;
}
const getUserName: GetUserName = function (user) {
  return user.name;
};

실제 함수 정의와 인터페이스에서의 매개변수 이름은 꼭 같을 필요는 없다. 

매개변수명을 user가 아닌 u로 바꾸어 써도 매개변수 타입 순서만 맞다면 에러는 발생하지 않는다.

const getUserName: GetUserName = function (u) {
  return u.name;
};

하이브리드 타입

자바스크립트에서는 jQuery의 $과 같이 호출 가능한(callable) 동시에 추가적으로 여러 속성을 갖는 객체가 존재할 수 있다. 

이런 객체의 타입을 표현하기 위해서 호출 시그니쳐와 속성 타입 정의를 동시에 적을 수 있다.

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}
function getCounter(): Counter {
    let counter = function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

Counter 타입의 값은 함수로서 호출 할 수 있고 따라서 호출 시그니쳐를 갖는다. 

이 인터페이스는 추가적으로 interval과 reset 이라는 속성을 가진다. 

따라서 인터페이스는 해당 속성의 타입 정보 또한 포함한다.
이렇게 호출 시그니쳐와 속성 타입을 동시에 갖는 인터페이스를 하이브리드 타입(hybrid type)이라 부른다.


제너릭 인터페이스

인터페이스 이름 뒤에 타입 변수 정의를 붙여 제너릭 인터페이스(generic interface)를 정의할 수 있다. 

 

서버로부터 받은 임의의 응답을 나타내는 Response 인터페이스를 아래와 같이 정의할 수 있다.

interface MyResponse {
  data: Data;
  status: number;
  ok: boolean;
  /* ... */
}
inteface User {
  name: string;
  readonly height: number;
  /* ... */
}
const user: MyReponse = await getUserApiCall(userId);
user.name; // 타입 시스템은 user.name이 string임을 알 수 있다.

함수 인터페이스의 정의에도 제너릭을 사용 할 수 있다. 타입 변수는 매개변수의 앞에 적는다.

interface GetData {
  (response: MyResponse): Data;
}

타입 별칭과의 차이

① 타입 별칭을 이용해서 기본 타입, 배열과 튜플, 유니온 타입 등에 새로운 이름을 붙일 수 있다 (type UUID = string). 

    인터페이스로는 해당 타입을 표현하는 것이 불가능하다.


② 타입 별칭은 실제로는 새 타입을 생성하지 않는다. 

    따라서 type User = { name: string; } 타입과 관련된 타입 에러가 발생했을 시 에러 메시지는 User 대신 

    { name: string; } 를 보여준다. 

    인터페이스는 실제로 새 타입을 생성하고, interface User { name: string; } 과 관련된 에러 메시지에는 User 가 등장한다.


③ 인터페이스는 extends 키워드를 이용해 확장할 수 있는 반면, 타입 별칭의 경우는 그런 수단을 제공하지 않는다.


이런 차이점 때문에 가능한 경우 항상 타입 별칭보다 인터페이스를 사용할 것을 권장한다.

인터페이스로 표현할 수 있는 모든 타입은 인터페이스로 표현하고, 기본 타입에 새로운 이름을 붙이고 싶거나

유니온 타입을 명명하고 싶은 경우 등 인터페이스의 능력 밖인 부분에서만 타입 별칭을 사용한다.


색인 가능 타입(indexable type)

색인 가능 타입을 이용해 색인 가능한(indexable) 객체의 타입을 정의할 수 있다. 

색인 가능 타입을 정의하기 위해서는 색인에 접근할 때 사용하는 기호인 대괄호([])를 이용해 객체의 색인 시그니쳐(index signature)를 적어줘야 한다.

예를 들어 NameHeightMap 인터페이스는 색인 가능 타입을 사용해 아래와 같이 적을 수 있다.

interface NameHeightMap { 
  [userName: string]: number | undefined; 
} 

NameHeightMap 타입의 값을
임의의 string 타입 값 userName 으로 색인한 값 ([userName: string]  → 인덱스 시그니쳐)
즉, nameHeightMap[userName]은 number 또는 undefined 타입의 값이다.

nameHeightMap이 모든 문자열을 키로 갖고 있다는 보장이 없으므로 

nameHeightMap['없는 유저'] 의 값은 undefined 일 수 있기 때문이다.

색인된 값을 number 타입의 값으로 사용하고 싶다면 먼저 undefined인지 여부를 체크해줘야 한다.

const h = nameHeightMap['a']; // 이 시점에서 h의 타입은 number | undefined 
if (h !== undefined) { 
  // 이 시점에서 h의 타입은 number 
  console.log(h.toString()); // ok 
}

색인과 타입

색인의 타입으로는 문자열 또는 숫자만이 사용 가능하다. 

문자열 색인과 숫자 색인이 모두 존재하는 경우, 숫자로 색인 된 값의 타입은 문자열로 색인 된 값 타입의 서브타입이어야 한다. 


즉, 아래에서 B는 A의 서브타입이어야 한다.

inteface Mixed<A, B> { 
  [stringIndex: string]: A; 
  [numberIndex: number]: B; 
}

이 때 “B가 A의 서브타입이다”의 의미는 “B 타입의 모든 값은 A 타입에도 속한다”로 이해할 수 있다. 

 

예를 들어, 모든 정수를 나타내는 타입 Int와 모든 숫자를 나타내는 타입 Num이 존재한다고 하자. 

모든 정수는 숫자이므로 (즉 Int 타입의 모든 값을 Num 타입의 값으로도 사용할 수 있으므로) Int는 Num의 서브타입이다.

 

읽기 전용 색인

색인 역시 읽기 전용으로 선언할 수 있다. 

객체 타입, 인터페이스에서의 readonly와 마찬가지로 readonly로 선언된 색인의 값은 재할당이 불가능하다.

interface x { 
  readonly [name: string]: y; 
} 
const m: x = { 'a': 1 }; 
m['a'] = 2; // error TS2542: Index signature in type 'x' only permits reading.


색인 가능 타입의 사용예

색인 가능 타입을 사용하는 인터페이스 중 하나로 Array 인터페이스가 있다. 

만약 색인 가능 타입이 없이 T 타입의 원소를 갖는 Array 인터페이스를 작성한다면

모든 색인에 대한 타입을 일일이 정의해야 할 것이다.

interface Array<T> { 
  length: number; 
  0?: T; 
  1?: T; 
  /* ... */ 
  Number.MAX_SAFE_INTEGER?: T; 
  /* 메소드 정의 */ 
}  

인덱스 타입을 이용하면 위 코드를 다음처럼 간결하게 대체할 수 있다.

interface Array<T> { 
  length: number; 
  [index: number]?: T; 
  /* 메소드 정의 */ 
}

인터페이스 확장(interface extends)

interface User { 
  name: string; 
  readonly height: number; 
  favoriteLanguage?: string; 
} 

User 인터페이스가 갖는 특성을 모두 가지면서 추가적인 속성을 갖는 타입을 정의하고 싶은 경우

extends 키워드를 이용한 인터페이스 확장을 통해 기존 인터페이스를 재사용 할 수 있다. 

interface LoggedInUser extends User { 
  loggedInAt: Date; 
}

LoggedInUser 인터페이스는 User 인터페이스의 모든 속성(name: string, readonly height: number, favoriteLanguage?: string)을 가지며, 

추가적으로 loggedInAt: Date 속성 또한 갖는다.

 

다수 인터페이스 확장

인터페이스는 동시에 하나 이상의 인터페이스를 확장할 수 있다.

이 경우 확장 대상이 되는 인터페이스들은 반점(,)으로 구분한다. 

새로 정의할 인터페이스는 모든 확장 대상 인터페이스의 속성에 자신의 속성을 더한 타입을 갖는다.

interface ElectricDevice { 
  voltage: number; 
} 
interface SquareShape { 
  width: number; 
  height: number; 
} 
interface Laptop extends ElectricDevice, SquareShape { 
  color: string; 
} 
const macbook15: Laptop = { voltage: 220, width: 30, height: 21; color: 'white' };

이 때, 확장 대상인 인터페이스 중 여러 인터페이스가 같은 이름의 속성을 가질 수 있다. 

그런 경우, 이름이 겹치는 속성의 타입은 모든 확장 대상 인터페이스에서 같아야 한다. 

만약 같은 이름의 속성이 다른 타입을 갖는 경우엔 타입 에러가 발생한다.

interface BeverageLover { 
  favoriteDrink: string; 
} 
interface BeerLover { 
  favoriteDrink: 'beer'; 
} 
interface CoolPerson extends BeverageLover, BeerLover { 
  name: string; 
} 
// error TS2320:  
// Interface 'CoolPerson' cannot simultaneously extend types 'BeverageLover' and 'BeerLover'. 
// Named property 'favoriteDrink' of types 'BeverageLover' and 'BeerLover' are not ide

클래스(class)

클래스(class)를 이용해 객체 지향 프로그래밍 언어와 비슷한 방식으로 코드를 구조화 할 수 있다. 

타입스크립트의 클래스는 ES6 클래스의 상위집합으로, ES6 클래스를 포함할 뿐 아니라 여러 추가기능을 제공한다. 

 

클래스 정의

class aa {}

정의된 클래스는 new 키워드를 이용해 인스턴스화(instantiation) 한다.

const bb: aa = new aa()

 

A라는 클래스를 정의할 때, 이름은 같지만 의미는 다른 두 식별자가 동시에 생성된다.


① 타입 A : A 클래스 인스턴스의 타입 (const a: A)
② 함수 A : new 키워드와 함께 호출되는 클래스 A의 생성자 (new A())


const a: A① = new A()②

앞의 A는 인스턴스의 타입을 가리키는 반면

뒤의 A는 해당 클래스의 생성자, 즉 함수 타입의 값이다.


생성자

constructor 키워드를 사용해 클래스 생성자(class constructor)를 정의할 수 있다.

만약 어떤 클래스가 생성자를 정의하지 않았다면, 본문이 비어있는 함수가 생성자로 사용된다. 

 

(constructor() {} )객체 생성자와 유사하게, 클래스 생성자를 통해 클래스 인스턴스가 생성될 때 실행될 로직을 정의할 수 있다.

class Dog {
  constructor() {
    console.log('constructing!');
  }
}
const dog: Dog = new Dog(); // constructing!

생성자는 임의의 매개변수를 취할 수 있다. 

이 변수들은 클래스를 인스턴스화 할 때 인자로 넘겨진다. 

아래에서는 생성자의 barkingSound 매개변수에 들어갈 값으로 인자 '월' 을 넘기고 있다.

class BarkingDog {
  constructor(barkingSound: string) {
    console.log(`${barkingSound}!`);
  }
}

const barkingDog: BarkingDog = new BarkingDog('월'); // 월!

생성자의 함수 시그니쳐와 맞지 않는 타입의 인스턴스화를 시도할 시 에러가 발생한다.

const err: BarkingDog = new BarkingDog(); 
// error TS2554: Expected 1 arguments, but got 0.

const err2: BarkingDog = new BarkingDog(3); 
// error TS2345: Argument of type '3' is not assignable to parameter of type 'string'.


속성(property)

객체 속성과 유사하게 클래스 인스턴스도 속성(property)를 가질 수 있다. 

클래스 내에서는 속성엔 this 키워드를 이용해 접근 가능하다. 

모든 클래스 속성은 이름: 타입 꼴의 속성 선언(property declaration)을 통해 타입을 표기해주어야 한다.

class Triangle {
  // 속성 선언
  vertices: number;
  constructor() {
    // 속성 할당
    this.vertices = 3;
  }
}
const triangle: Triangle = new Triangle();
console.log(triangle.vertices); // 3

--strictPropertyInitialization 옵션

--strict 옵션은 --strictPropertyInitialization 옵션을 포함한다. 

이 옵션이 켜진 환경에선 undefined 를 포함하지 않는 클래스 속성이 반드시 속성 선언 또는 

생성자, 두 장소 중 한 곳에서 초기화 되어야 한다. 


생성자와 속성 선언 두 곳 모두에서 초기화되지 않는 경우 속성 값이 undefined일 가능성이 존재한다. 

따라서 타입스크립트는 그 정보가 타입에 포함되어 있지 않다면 타입 에러를 낸다. 

class User {
  private password: string;
}
// error TS2564: Property 'password' has no initializer and is not definitely assigned in the constructor.

password는 속성 선언에서도, 생성자에서도 초기화되지 않고 있으므로 접근되는 시점에 undefined 일 수 있다. 

이 값은 string 타입이 아니므로 타입 에러가 발생하는 것이다. 

 

이 에러를 없애기 위해선 두 가지 방법이 존재한다.
① password를 선택 속성으로 선언한다.
     password: string 를 password?: string (또는 password: string | undefined) 로 변경한다.


② password가 undefined여서 문제가 생길 일이 없다고 확신한다면, 

    속성 이름 뒤에 느낌표(!)를 붙여 확정적 할당 단언(definitive assignment assertion)을 제공할 수 있다.
    password: T를 password!: T로 변경한다. 이 경우 컴파일러는 password의 초기화 체크를 건너뛴다.


속성 기본값

함수의 기본 인자와 유사하게 클래스 속성에도 기본값을 제공할 수 있다.

class Triangle {
  vertices: number = 3;
}
const triangle: Triangle = new Triangle();
console.log(triangle.vertices); // 3

읽기 전용 속성

readonly 키워드를 사용해 읽기 전용 속성을 정의할 수 있다.

속성 선언 또는 생성자 외의 장소에서는 읽기 전용 속성에 값을 할당 할 수 없다.

class Triangle {
  readonly vertices: number;
  constructor() {
    this.vertices = 3;
  }
}
const triangle: Triangle = new Triangle();
triangle.vertices = 4;
// error TS2540: Cannot assign to 'vertices' because it is a constant or a read-only property.

이 때, 읽기 전용 속성은 --strictPropertyInitialization 옵션 여부와 무관하게 

반드시 속성 선언 또는 생성자에서 초기화되어야 한다. 

읽기 전용이라는 정의 자체에 의해 그 두 곳 이외에선 할당 자체가 불가능하기 때문이다. 

만약 둘 중 어느 곳에서도 초기화 되지 않았다면 에러가 발생한다.

class Triangle {
  vertices: number;
  readonly angles: number[];
  constructor() {
    this.vertices = 3;
  }
}
// error TS2564: Property 'angles' has no initializer and is not definitely assigned in the constructor.

메소드

객체의 단축 메소드명과 유사한 문법을 사용해 인스턴스의 메소드를 정의할 수 있다. 

메소드 내에서는 this 키워드를 사용해 해당 메소드가 호출되는 인스턴스를 참조할 수 있다. 

 

예를 들어, 메소드를 사용해 위의 예제를 아래와 같이 변경할 수 있다.

class BarkingDog {
  barkingSound: string;
  
  constructor(barkingSound: string) {
    this.barkingSound = barkingSound;
  }
  
  bark(): void {
    console.log(`${this.barkingSound}!`);
  }
}
const barkingDog: BarkingDog = new BarkingDog('월');
barkingDog.bark(); // 월!

위에서처럼 함수를 속성으로 정의한 경우엔 별도의 속성 선언이 필요하지만, 

메소드 문법을 사용할 경우 속성 선언을 생략할 수 있다.

 

클래스 확장

인터페이스 확장과 유사하게, 클래스 역시 extends 키워드를 사용해 기존에 존재하는 클래스를 확장할 수 있다. 

 

클래스 A가 클래스 B를 확장 할 때,
  A를 B의 서브클래스(subclass)
  B를 A의 슈퍼클래스(superclass)
라고 부른다. 

 

인터페이스 확장과 마찬가지로, 서브클래스는 슈퍼클래스의 멤버(속성, 메소드)를 갖는다.

class Base { 
  answer: number = 42; 
  greetings() { 
    console.log('Hello, world!'); 
  } 
} 
class Extended extends Base { } 
const extended: Extended = new Extended(); 
console.log(extended.answer); // 42 
extended.greetings(); // Hello, world!

클래스 확장 시 생성자

슈퍼클래스의 생성자는 서브클래스의 생성자에서 자동 호출되지 않는다. 

따라서 서브클래스의 생성자에선 반드시 super 키워드를 사용해 슈퍼클래스의 생성자를 호출해줘야 한다.

class Base { 
  baseProp: number; 
  constructor() { 
    this.baseProp = 123; 
  } 
} 
class Extended extends Base { 
  extendedProp: number; 
  constructor() { 
    super(); // 반드시 이 호출을 직접 해 주어야 한다. 
    this.extendedProp = 456; 
  } 
} 
const extended: Extended = new Extended(); 
console.log(extended.baseProp); // 123 
console.log(extended.extendedProp); // 456

만약 서브클래스 생성자에서 슈퍼클래스 생성자의 호출을 빠트릴 경우 에러가 발생한다.

class ExtendedWithoutSuper extends Base { 
  constructor() { } 
}  
// error TS2377: Constructors for derived classesmust contain a 'super' call.

스태틱 멤버(static member)

지금까지 속성과 메소드는 클래스의 인스턴스 별로 각각 생성되고 관리되었다. 

클래스 전체에서 공유되는 값이 필요한 경우, 스태틱 멤버(static member)를 사용할 수 있다.

스태틱 멤버에는 클래스 이름을 사용해 접근 할 수 있다.

스태틱 속성
속성 선언 앞에 static 키워드를 붙여 스태틱 속성을 정의한다. 

 

아래는 count 라는 이름의 스태틱 속성을 정의하고 접근하고 있다.

스태틱 속성이므로 this.count가 아닌 Counter.count 와 같은 방식으로 접근함을 볼 수 있다.

class Counter { 
  static count: number = 0; 
} 
console.log(Counter.count); // 0

스태틱 메소드
메소드 선언 앞에 static 키워드를 붙여 스태틱 메소드를 정의할 수 있다.

class Counter { 
  static count: number = 0; 
  static increaseCount() { 
    Counter.count += 1; 
  } 
  static getCount() { 
    return Counter.count; 
  } 
} 
Counter.increaseCount(); 
console.log(Counter.getCount()); // 1 
Counter.increaseCount(); 
console.log(Counter.getCount()); // 2

접근 제어자(access modifier)

접근 제어자를 사용해 인스턴스의 멤버에 대한 접근 권한을 지정할 수 있다.
접근 제어자는 타입스크립트의 기능으로, ES6 클래스에는 포함되지 않는다.

① public
public 멤버는 접근 제한이 전혀 존재하지 않으며, 프로그램의 어느 곳에서나 접근 가능하다.

접근 제어자가 명시되지 않은 멤버는 모두 암시적으로 public 접근 권한을 갖는다. 

 

아래의 두 정의는 의미상 동일하다.

// implicit public member 
class Triangle { 
  vertices: number; 
   
  constructor() { 
    this.vertices = 3; 
  } 
} 

// explicit public member 
class Triangle { 
  public vertices: number; 
   
  public constructor() { 
    this.vertices = 3; 
  } 
} 

② private
private 멤버에는 해당 클래스 내부의 코드만이 접근 가능하다. 

만약 클래스 바깥에서 private 멤버에 접근하려 할 시 에러가 발생한다.

class User { 
  private password: string; 
   
  constructor (password: string) { 
    this.password = password; 
  } 
} 

const yoonha = new User('486'); 
console.log(yoonha.password);  
// error TS2341: Property 'password' is private and only accessible within class 'User'. 

private 멤버의 접근 제한은 서브클래스에도 적용된다. 

기본적으로 서브클래스는 슈퍼클래스의 멤버에 접근할 수 있지만, 만약 해당 멤버가 private 멤버라면 접근할 수 없다.

class CarOwner extends User { 
  carId: string; 
   
  constructor (password: string, carId: string) { 
    super(password); 
    this.carId = carId; 
  } 
   
  setPassword(newPassword: string) { 
    this.password = newPassword; 
    // error TS2341: Property 'password' is private and only accessible within class 'User'. 
  } 
 } 

③ protected
protected 권한의 멤버는 private과 비슷하게 동작하지만, 서브클래스에서의 접근 또한 허용된다는 점이 다르다. 

위의 예시에서 User의 멤버 password의 접근 제어자를 private에서 protected로 변경하면 에러가 사라진다.

class User { 
  protected password: string; 
   
  constructor (password: string) { 
    this.password = password; 
  } 
} 
 
class CarOwner extends User { 
  carId: string; 
   
  constructor (password: string, carId: string) { 
    super(password); 
    this.carId = carId; 
  } 
   
  setPassword(newPassword: string) { 
    this.password = newPassword; 
    // Okay 
  } 
} 

생성자에서의 접근 제어자

멤버 선언 외에도 생성자의 매개변수 앞에 접근 제어자를 명시할 수 있다. 

 

접근 제어자가 붙은 생성자 매개변수는 같은 이름의 속성으로 선언되고, 

해당 매개변수의 인자는 암묵적으로 인스턴스에 할당된다. 

 

다음 코드는 아래와 동일하게 동작한다. 

class User { 
  constructor (public id: string, private password: string) { } 
}

class User { 
  public id: string; 
  private password: string; 
   
  constructor (id: string, password: string) { 
    this.id = id; 
    this.password = password; 
  } 
} 

접근자(accessor)

접근자는 이름 그대로 클래스 속성에 대해 접근할 때 실행될 로직을 정의하는 함수다.

멤버 접근은 크게 두 가지로 나뉘고, 대응하는 접근자 역시 두 가지가 존재한다.

① 읽기 접근(const value = instance.prop) : 게터(getter)
② 쓰기 접근(instance.prop = value) : 세터(setter)

 

읽기 접근을 위한 게터

속성 이름의 메소드 정의 앞에 get 키워드를 붙여 게터(getter), 즉 속성의 값을 읽어올 때 실행할 함수를 정의할 수 있다. 

게터는 인자를 받을 수 없으며, 게터의 반환값은 외부에 해당 속성의 값으로 노출된다.

class Shape { 
  constructor (public vertices: number) { } 
  get vertices(): number { 
    console.log('Vertices getter called.'); 
    return 3; 
  } 
} 
const triangle: Shape = new Shape(3); 
const vertices = triangle.vertices; // Vertices getter called. 
console.log(vertices); // 3 

쓰기 접근을 위한 세터

속성 이름의 메소드 정의 앞에 set 키워드를 붙여 세터(setter), 즉 속성의 값을 쓸 때 실행될 함수를 정의할 수 있다.

세터는 새로 할당되는 값을 인자로 받는다.

class Shape { 
  private _vertices: number = 3; 
  get vertices() { 
    console.log('Vertices getter called.'); 
    return this._vertices; 
  } 
  set vertices(value) { 
    console.log('Vertices setter called.'); 
    this._vertices = value; 
  } 
} 
const square = new Shape(); 
square.vertices = 4; // Vertices setter called. 
const vertices = square.vertices; // Vertices getter called. 
console.log(vertices); // 4 

Shape 클래스는 내부적으로만 접근 가능한 private 속성(_vertices)을 유지하며, 

외부에서는 vertices 게터와 세터를 통한 접근만을 허용한다. 


만약 게터만 존재하고 세터가 존재하지 않는 멤버의 경우, 읽기만 가능할 뿐 쓸 수단이 없다. 

따라서 타입스크립트는 해당 멤버를 readonly 로 인식한다.


​추상 클래스(abstract class)

class 키워드 대신 abstract class 키워드를 사용해 추상 클래스를 선언할 수 있다. 

일반 클래스는 extends 키워드를 사용해 추상 클래스를 확장할 수 있다. 

추상 클래스는 인스턴스화가 불가능하다는 점에서 일반 클래스와 다르다. 

또한 추상 클래스는 구현을 일부 포함할 수 있다는 점에서 인터페이스와 다르다. 

abstract class Animal { 
    move(): void { 
        console.log("roaming the earth..."); 
    } 
    abstract makeSound(): void; 
} 

abstract class 키워드를 이용해 정의된 추상 클래스 Animal은 두 멤버를 갖는다. 

먼저 move 메소드는 일반 클래스 메소드와 동일하다. 

한 편, abstract 키워드가 앞에 붙어 있는 makeSound 는 가상 메소드(abstract method)로, 

타입 정보 이외의 실제 구현은 포함하지 않고 있다.

가상 클래스를 확장하는 서브 클래스는 슈퍼 클래스의 모든 가상 메소드를 구현해야 한다. 

만약 이 예시에서 Animal을 확장하는 일반 클래스가 makeSound 메소드를 올바르게 구현하지 않는다면 타입 에러가 발생한다.

class Dog extends Animal { } 
// error TS2515: Non-abstract class 'Dog' does not implement inherited abstract member '

인터페이스와 클래스의 관계

값이 가져야 하는 특정한 형태를 기술한다는 점에서 유사하다.

인터페이스 또는 클래스 확장과 유사하게 인터페이스와 클래스 사이에도 연결고리를 생성하는 방법이 존재한다.


클래스의 인터페이스 구현

인터페이스는 본질적으로 값이 어떤 멤버를 반드시 가져야 하며 그 멤버들의 타입은 어때야 한다는 제약을 나타내는 수단이다.

 

implements 키워드를 사용해 클래스가 이러한 제약을 따라야 함을 표현할 수 있다. 

interface Animal { 
  legs: number; 
} 
class Dog implements Animal { }

이 코드는 다음 에러를 발생시킨다.

error TS2420: Class 'Dog' incorrectly implements interface 'Animal'. 
  Property 'legs' is missing in type 'Dog'. 

Dog 클래스가 따라야 할 제약인 Animal 인터페이스에 따르면 legs: number 속성이 존재해야 하는데, 그렇지 않다. 

해당 속성을 추가해 클래스가 인터페이스를 구현하도록 변경하면 에러는 사라진다.

interface Animal { 
  legs: number; 
} 
class Dog implements Animal { 
  legs: number = 4; 
} 
// Okay 

인터페이스 구현 문법을 사용해 클래스가 특정 인터페이스를 따르도록 할 수 있다.

인터페이스의 클래스 확장

인터페이스가 기존에 존재하는 클래스의 형태를 확장하는 것 또한 가능하다.

인터페이스 확장과 유사하게 extends 키워드를 사용해 클래스를 확장할 수 있다. 

class Point { 
    x: number; 
    y: number; 
} 
interface Point3d extends Point { 
    z: number; 
} 
const point3d: Point3d = {x: 1, y: 2, z: 3}; 

Point3d 인터페이스는 자신의 z: number 속성 이외에도 

Point 클래스의 멤버인 x: number, y: number 속성을 가진다.


참고사이트

https://ahnheejong.gitbook.io/ts-for-jsdev

 

 

'do > typescript' 카테고리의 다른 글

typescript 5  (0) 2019.04.16
typescript 3  (0) 2019.04.12
typescript 2  (0) 2019.04.10
typescript 1  (0) 2019.04.09
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함