favicon

Jayden { do: smite }

(러닝 타입스크립트) 7~8장

🏓 기술책 스터디

23년 1월부터 활동 중인 교육에서, 뜻이 맞는 동료들과 함께 진행하게 된 스터디<br/> 앞으로도 꾸준히 기술 서적을 읽고 함께 발전하는 시간이 되었으면 좋겠다!

7장. 인터페이스

  • 인터페이스는 객체 형태를 설명하는 또 다른 방법
  • types alias로 정의된 객체 타입과 유사하지만 차별점 존재
    • 더 읽기 쉬운 오류 메시지
    • 더 빠른 컴파일러 성능
    • 클래스와의 더 나은 상호 운용성

Type alias VS Interface

두 가지 모두 세미콜론과 쉼표 둘 다 가능하다.

인터페이스의 특징

  • 속성 증가를 위해 병합이 가능하다.
  • 클래스가 선언된 구조의 타입을 확인하는데 사용 가능하다.
  • 일반적으로 인터페이스에서 타입 검사기가 더 빨리 작동한다. 내부적으로 더 쉽게 캐시할 수 있는 명명된 타입을 선언하기 때문이다.
  • 이름 없는 객체 리터럴의 별칭이 아닌 이름이 있는 하나의 객체로 간주되므로 오류 메시지를 좀더 쉽게 읽을 수 있다.

읽기 전용 속성(readonly)

  • 인터페이스에 정의된 객체의 속성을 재할당하지 못하도록 차단할 때, readonly 키워드를 추가한다.
  • readonly 연산자는 타입 시스템에만 존재하며 인터페이스에서만 사용할 수 있다. 단지, 개발 중에 그 속성이 수정되지 못하도록 보호한다.

메서드와 속성 함수

  • 메서드 구문: test(): void;
  • 속성 함수 구문: test: () => void;

메서드는 readonly 선언이 불가능, 속성 함수는 가능

호출 시그니처

함수 타입과 유사하지만, 콜론(:)이 아닌 화살표(=>)로 표시한다.

interface TestImpl { (input: string): number; } const test: TestImpl = (input: string) => input.length;

인덱스 시그니처

  • 자바스크립트 객체 속성 조회는 암묵적으로 키를 문자열로 변환하기 때문에 인터페이스의 객체는 일반적으로 문자열 키를 사용한다.
interface Example { [i: string]: number; }
  • 인덱스 시그니처는 객체에 값을 할당할 때 편리하지만 타입 안정성을 완벽하게 보장하지는 못한다.
  • 명명된 속성이 더 구체적인 타입을 제공하고, 다른 모든 속성은 인덱스 시그니처의 타입을 대체하는 것으로 혼합해서 사용할 수 있다.
  • 일반적으로는 인덱스 시그니처의 원시 속성보다 더 구체적인 속성 타입 리터럴을 사용한다.
interface Dog { name: 'hodu'; // 더 구체적인 리터럴 [i: string]: string; }

인터페이스 확장

  • 인터페이스가 다른 인터페이스의 모든 멤버를 복사해서 선언할 수 있는 확장된 인터페이스
  • 확장할 인터페이스의 이름 뒤에 extends 키워드를 추가한다.

재정의된 속성

  • 타입 검사기는 재정의된 속성이 기본 속성에 할당되어있도록 강요한다.
  • 즉, 재정의한 속성은 이전 정의보다 더 좁은 범위여야 한다.

다중 인터페이스 확장

  • 인터페이스는 여러 개의 인터페이스를 확장해서 선언할 수 있다.
  • extends 키워드 뒤에 쉼표로 인터페이스 이름을 구분해 사용하면 된다.

인터페이스 병합

  • 여러 개의 인터페이스가 동일한 이름으로 동일한 스코프에 선언된 경우, 선언된 모든 필드를 포함하는 더 큰 인터페이스가 된다.
  • 인터페이스 병합을 자주 사용하지는 않는다.
  • 인터페이스 병합은 외부 패키지 또는 Window와 같은 내장된 전역 인터페이스를 보강하는데 특히 유용하다.

이름이 충돌되는 멤버

  • 속성이 이미 인터페이스에 선언되어 있다면 나중에 병합된 인터페이스에서도 동일한 동일한 타입을 사용해야 한다.
  • 같은 이름의 속성이 타입이 서로 다르면 오류가 발생한다.
  • 다만, 동일한 이름과 다른 시그니처를 가진 메서드는 정의할 수 있다.(함수 오버로드)

8. 클래스

명심할 내용: 타입스크립트는 클래스 사용이나 다른 인기 있는 자바스크립트 패턴을 권장하지도 막지도 않는다.

개인적인 생각: 그렇지만 자바 언어가 클래스로 객체를 표현하는 객체 지향 언어인만큼 타입스크립트도 클래스로 구현을 많이 하는 것 같다.

클래스 속성

  • 타입스크립트에서 클래스의 속성을 읽거나 쓰려면 클래스에 명시적으로 선언해야한다.
class Example { test: string; // 이런 식으로 속성의 타입을 명시적으로 선언해야된다. constructor() { this.test = 'ttt'; } }

타입스크립트는 생섲아 내의 할당에 대해서 그 멤버가 클래스에 손재하는 멤버인지 추론하려고 시도하지 않습니다. 즉, 위처럼 명시를 해주지 않으면 TS가 스스로 추론하지 못하는 것을 의미한다.

함수 속성

class T { arrow: () => void; constructor() { this.arrow = () => { console.log(this) } } nonArrow() { console.log(this) } } const t = new T(); t.arrow(); // 'T: {}'가 찍힌다. t.nonArrow(); // 'T: {}'가 찍힌다. t.arrow.bind('arrow입니다.')(); // 'T: {}'가 찍힌다. t.nonArrow.bind('메서드입니다.')(); // '메서드입니다.'가 찍힌다.
class T { t = 111; } // 위와 아래는 같은 표현이다. class T { t: number; constructor() { this.t = 111 } }

초기화 검사

  • 엄격한 컴파일러 설정이 활성화된 상태에서는 타입스크립트가 undefined 타입으로 선언된 각 속성이 생성자에서 할당되었는지 확인한다.

엄격한 초기화 검사를 적용하면 안되는 속성인 경우에는 이름 뒤에서 !를 추가하여 검사를 비활성화할 수 있다. 그러나 이는 type을 any로 두는 것과 같이 타입 안정성을 줄이는 행위이므로 가급적이면 코드를 리팩토링하자.

읽기 전용 속성

  • readonly로 선언된 속성은 선언된 위치 또는 생성자에서 초깃값만 할당할 수 있다. 해당 속성은 읽을 수만 있고 값을 변경할 수는 없다.
  • let이 아닌 const로 변수를 할당하는 것과 유사하다.(추후 초기화 및 새로운 값 할당이 불가능하다.)

readonly는 TS에만 존재하는 기능이다. 그러므로 진정한 읽기 전용 보호가 필요하다면 private이나 getter를 사용하자.

타입으로서의 클래스

  • 타입 시스템에서의 클래스는 런타임 시의 값(클래스 객체 자체)과 타입으로서도 생성된다.
  • 클래스의 동일한(이름이 같은) 멤버를 모두 포함하는 모든 객체 타입을 그 클래스에 할당할 수 있는 것으로 간주한다.
    • 이는 타입스크립트의 구조적 타이핑이 선언되는 방식이 아니라 객체의 형태만 고려하기 때문이다.
class Example { test: () => void; constructor() { this.test = () => { console.log('test'); } } } const example: Example = { test() { console.log('객체입니다.') } } // class 생성자를 통해 생성된 객체가 아닌, 객체 리터럴임에도 동일한 멤버만 갖고 있다면 할당이 가능하다.

어떻게 보면 조금은 허술해보일 수 있지만, 실제로 우리가 코드를 작성할 때 class로 선언된 타입을 가져와서 객체 리터럴로 값을 할당하는 일은 거의 없다.

클래스와 인터페이스

  • 클래스 이름 뒤에 implements 키워드와 인터페이스 이름을 추가함으로써 클래스의 인스턴스가 이 인터페이스를 따른다고 선언할 수 있다.
interface ExampleImpl { test: string; } class Example implements ExampleImpl { test: string; constructor() { this.test = 'test입니다.'; } }
  • 타입스크립트는 인터페이스에서 클래스의 메서드 또는 속성 타입을 유추하지 않는다.
interface ExampleImpl { test: string; do: (something: string) => void; play: (something: string) => void; } class Example implements ExampleImpl { test; // 프로퍼티는 에러 안남. play; // 프로퍼티로 선언된 함수 자체는 에러 안남. constructor() { this.test = 'test입니다.'; this.play = (something) => { // Parameter 'something' implicitly has an 'any' type. 여기서 타입 에러 발생 console.log(`Let's play ${something}`) } } do(something) { // Parameter 'something' implicitly has an 'any' type. console.log(something); } } // 즉, something 인자는 string으로 추론되지 못하고 any 타입으로 지정된다. 그러니까 위와 같은 any 타입 오류가 나오는 것이다.

인터페이스를 구현하는 것은 순전히 안정성 검사를 위함이다!

다중 인터페이스 구현

interface FirstImpl {} interface SecondImpl {} class Example implements FirstImpl, SecondImpl {} // 두개의 인터페이스의 규칙을 모두 다 지켜야 한다.

추상 클래스(Abstract Class)

일부 메서드의 구현을 선언하지 않고, 하위 클래스가 해당 메서드를 제공할 것을 예상하여 기본 클래스를 만들 때 사용한다.

  • class 앞에 abstract를 추가한다.
  • 생성자로서의 역할은 하지 못한다.

멤버 접근성

  • public(default): 어디서나 접근 가능
  • protected: 해당 클래스 내부 또는 하위 클래스에서만 접근 가능
  • private: 해당 클래스 내부에서만 접근 가능

JS에서는 암묵적으로 protected는 _ 표시하고 private는 #으로 정말 private의 기능을 가진다. TS의 멤버 접근성은 타입 시스템에만 존재하는 반면, JS의 private는 런타임에도 존재한다.

정적 필드 제한자

  • class에서 static 또한 readonly가 적용된다.

Copyright 2023. all rights reserved by Jayden