교육 (Today I Learned)/Hanaro

[Hanaro] 20일차 / TypeScript (class, 타입 제한자, Generic)

Bay Im 2024. 2. 15. 09:16

TypeScript - 클래스

  • 클래스 메서드
    • 독립 함수와 동일한 방식의 메서드
    • 매개변수 타입이나 기본 값을 지정하지 않으면 any 타입을 기본 값으로 가진다.
    • 메서드를 호출하려면 허용 가능한 수의 인수가 필요하다. 재귀 함수가 아니라면 대부분 반환 타입 유추 가능
    • 메서드 호출 시 올바른 타입의 인수가 올바른 수로 제공되는지 확인하기 위해 타입 검사 실시
  • 클래스 속성(attribute)
    • 클래스의 속성을 읽거나 쓰려면 명시적으로 선언해야 한다.
  • 클래스 속성 - method
    • 모든 prop은 초기화 또는 정의해야 한다. (! 또는 ?는 제외)
  • 클래스 속성 - 초기화 검사
    • StrictNullCheck On에서 TS는 undefined 타입으로 선언된 각 속성이 생성자에서 할당되었는지 확인
    • 초기화하지 않았다는 것은 undefined이므로 생성자에서라도 초기화 해야 한다.
    • StrictNullCheck가 없다면 타입 시스템이 undefined 값에 접근할 수 없다고 말할지라도 클래스 인스턴스는 undefined 값에 접근할 수 있다.
  • 클래스 속성 - 확실하게 할당된 속성
    • 엄격한 초기화 검사를 적용하면 안되는 속성인 경우 변수 이름 뒤에 ‘!’를 추가해 검사를 비활성화하도록 하기
    • TS 속성이 처음 사용되기 전에 undefined 값이 할당됨
    • 또는 생성자에서 초기화
      • constructor(변수: string[]) { this.변수 = 변수; }
  • 클래스 속성 - 선택적 속성
    • 선언된 속성 이름 뒤에 ?를 추가해 속성을 옵션으로 선언
      • class 클래스 { 변수?: string; }
    • ‘ | undefined’ 를 포함하는 유니언 타입과 동일하게 작동
  • 클래스 속성 - 읽기 전용 속성 (readonly)
    • 선언된 속성 이름 앞에 readonly 키워드를 추가해 속성을 읽기 전용으로 선언
    • class 클래스 { readonly 변수: string; constructor(변수: string) { this.변수 = 초기화값; }
    • readonly로 선언된 속성은 선언된 위치 또는 생성자에서 초기값만 할당할 수 있다. (다른 모든 위치는 읽을 수 만 있고 쓸 수 없음)
    • readonly는 타입 시스템에만 존재하고 JS로 컴파일할 때는 삭제된다.
  • 클래스 속성 - readonly literal
    • 값의 타입이 가능한 좁혀진 리터럴 타입으로 유추
      • ex)
      class 클래스 {
      	readonly 변수1: string = 초기화값;
      	readonly 변수2 = "Hello";  // Literal Type
      }
      
      // 변수1의 타입은 string
      // 변수2의 타입은 "Hello"
      
  • 타입으로서의 클래스
    • TS는 클래스의 동일한 멤버를 모두 포함하는 모든 객체 타입을 클래스에 할 수 있는 것으로 간주
    • TS의 구조적 타이핑이 선언되는 방식이 아니라 객체의 형태만 고려하기 때문
  • 클래스와 인터페이스 (implements)
    • 클래스 이름 뒤에 implements 키워드와 인터페이스 이름을 추가하면 클래스의 인스턴스가 해당 인터페이스를 준수한다고 선언할 수 있다.
      • class 클래스 implements 인터페이스 { … }
    • 부모 클래스는 선언만 하며, 자식 클래스는 부모의 기능을 다시 정의해서 사용해야 한다. (오버라이딩)
  • 클래스와 인터페이스 - 다중 인터페이스 구현
    • 클래스에 구현된 인터페이스 목록은 개수 제한없이 인터페이스를 사용할 수 있다. (다 상속 가능)
      • class 클래스 implements 인터페이스1, 인터페이스2 { … }
    • 하지만 두 개의 충돌하는 인터페이스를 구현하는 클래스를 선언하려고 하면 클래스에 하나 이상의 타입 오류 발생
  • 클래스 확장 (extends)
    • 다른 클래스를 확장하거나 하위 클래스를 만드는 자바스크립트 개념에 타입 검사 추가
      • class 하위클래스 extends 기본클래스 { … }
    • 기본 클래스에 선언된 모든 메서드나 속성은 하위 클래스(파생 클래스) 에서 사용 가능
    • 부모 클래스에서 선언과 정의를 모두하고, 자식 클래스는 오버라이딩 할 필요없이 부모 클래스의 기능을 사용할 수 있다.
    • extends는 클래스 한 개만 상속받을 수 있다.
  • 클래스 확장 - 재정의(override)된 생성자
    • 하위 클래스가 자체 생성자를 선언하면 super 키워드를 통해 기본 클래스 생성자를 호출해야 함
    • class 하위클래스 extends 기본클래스 { constructor() { super(값); } }
    • 하위 클래스 생성자는 모든 매개변수 선언 가능
  • 클래스 확장 - 재정의(override)된 메서드
    • 하위 클래스는 기본 클래스와 동일한 이름으로 새 메서드를 다시 선언할 수 있다.
  • 클래스 확장 - 재정의된 속성
    • 하위 클래스는 동일한 이름으로 기본 클래스의 속성 (변수)을 명시적으로 다시 선언할 수 있다. (하위는 기본과 구조적으로 일치해야함)
  • 추상 클래스 (Abstract Class)
    • 일부 메서드를 구현하지 않고 하위 클래스가 해당 메서드를 제공할 것을 예상하고 기본 클래스를 만드는 방법
    • abstract class 추상클래스 { readonly name: string; constructor(name: string) { this.name = name; } // 아래 코드 처럼 본체는 없음 abstract 추상메서드(): string[]; } class 하위클래스 extends 추상클래스 { 추상메서드() { // 구현코드 } }
    • 추상 클래스를 상속 받는 하위 클래스는 꼭 추상 메서드를 재정의 해야 한다. (override 안하면 error 발생)
    • 추상 클래스는 객체 생성이 불가능하고, 자식 클래스를 대상으로 객체 생성 한다.
    • 추상 클래스는 추상 메서드를 가진 클래스를 말하며 일반 멤버(변수, 메서드)도 가질 수 있다. 하지만 한 개 이상의 추상 메서드는 반드시 존재해야함!
  • 멤버 접근성
    • TS의 멤버 접근성은 클래스 멤버의 선언 이름 앞에 아래 키워드 중 하나를 추가한다.
    • public (기본값)
      • 모든 곳(외부, 하위) 에서 접근 가능
    • protected
      • 해당 클래스 내부 또는 하위 클래스에서만 접근 가능
    • private
      • 해당 클래스 내부에서만 접근 가능
  • 멤버 접근성 - 정적 필드 제한자 (static)
    • 클래스를 통해 인스턴스를 생성할 필요 없이 클래스의 속성이나 메서드를 사용하고자 할 때 static 키워드를 사용하여 속성이나 메서드 정의
      • ex) protected static readonly 변수: ‘aaa’;
    • static 키워드를 사용하여 선언된 변수, 함수는 정적 변수, 정적 함수라고 말한다. 이는 클래스에 속하는 변수와 함수로 호출하기 위한 객체가 필요하지 않고 클래스에서 바로 호출 가능하다. (클래스에서 점 표기법으로 직접 호출가능)
    • 객체에서(인스턴스 생성) 정적 변수, 정적 함수 호출은 불가능
      • var 클래스2 = new 클래스();
      • 클래스2.정적함수(); → 에러 발생!

 

TypeScript - 타입 제한자

  • top 타입 (any, unknown)
    • top 타입은 모든 타입이 할당 가능한 타입
    • any
      • TS가 해당 값에 할당 가능성 또는 멤버에 대해 타입 검사를 수행하지 않도록 명시적으로 지시 (타입 아직 미정의)
      • const anyParam = (value: any) ⇒ { … }
        • 위 함수는 오류는 발생하지 않지만 런타임 에러 발생
    • unknown
      • unknown 타입 값의 속성에 직접 접근할 수 없고 top 타입이 아닌 타입에는 할당할 수 없다.
      • const anyParam = (value: unknown) ⇒ { … }
      • instanceof나 typeof를 사용하여 타입을 narrowing 하거나 타입 어서션을 통해 값의 타입이 제한된 경우에 해당 타입이 갖는 속성에 접근할 수 있다.
      • const anyParam = (value: unknown) => { if (**typeof** value === 'string') { ... } }
  • 타입 서술어
    • isString() 함수처럼 인자가 특정 타입인지 여부를 나타내기 위해 boolean 값을 반환하는 함수를 위한 구문
    • const isString = (value: unknown): **value is string** => typeof value === 'string';
  • 타입 연산자 - typeof
    • 제공되는 값의 타입 반환
    • const customer1 = { name: 'Im', mobile: '8282', }; // {name: string, mobile: string} 반환 let customer: typeof customer1;
  • 타입 연산자 - keyof
    • 존재하는 타입의 키를 바탕으로 유니언 타입 생성
    • const f3 = (customer: 객체, key: **keyof** 객체) => { … }
    • 제공되는 값에 존재하는 키만 매개변수의 타입으로 허용하고 싶을 때 typeof 함께 사용
    • const 변수 = (key: **keyof typeof 객체**)=> 객체[key];
  • 타입 어서션 (as)
    • 값의 타입을 재정의 (확신할 수 있는 경우에만)
    • const 변수 = JSON.parse(someData) **as 객체**;
  • non-null 어서션
    • null 또는 undefined를 포함할 수 있는 변수에서 null과 undefined를 제거한다. (느낌표)
      • const jang = 배열.find((a) => a.name === 'jang');
      • let 변수: number = jang**!**.age;

 

TypeScript - 제네릭

  • 제네릭 함수
    • 호출하는 방식에 따라 다양한 타입으로 작동하도록 의도한다.
    • 함수의 기능이 똑같은데 매개변수의 타입과 반환하는 타입이 다를 때 제네릭 기법을 사용하여 한 개의 함수로 구현 가능
    • 제네릭 함수 구현
      • function 제네릭함수<T>(arg: T): T { return arg; }
        • 함수명 뒤에 <T>를 추가하고, T를 매개변수의 타입 또는 반환 타입으로 설정 가능
        • 꼭 <T>로 작성할 필요는 없고 다른 문자열도 사용 가능 (대부분 Type의 T로 작성)
    • 제네릭 함수 호출
      • <>안에 인수의 타입을 작성한다.
        • ex) let 변수1 = 제네릭함수<number>(123);
        • ex) let 변수2 = 제네릭함수<string>(’ABC’);
    • 다중 함수 타입 매개변수
      • 구현
        • function 다중제네릭함수<First, Second> (first: First, second: Second_ { return [first, second] as const; }
      • 호출
        • ex) const [name, age] = 다중제네릭함수(’Im’, 25);
        • ex) const [level, agree] = 다중제네릭함수<number, boolean>(10, true);
  • 제네릭 인터페이스
    • 인터페이스에서도 제네릭을 사용해 항목의 타입 정의 가능
    • // 구현 interface 제네릭인터페이스<T> { id: number, name: string, additional?: T; } // 호출 type Color = 'red' | 'blue' | 'green'; const info1: 제네릭인터페이스<Color> = { id: 1, name: 'lim', additional: 'red' }; type Address = { sigungu: string, zipcode: string }; const info2: Info<Address> = { id: 2, name: 'hong', additional: { sigungu: 'Seoul', zipcode: '04112'} }
  • 제네릭 인터페이스 구현
    • class 클래스<T> implements 제네릭인터페이스<T> { … }
  • 제네릭 클래스
    • 클래스도 제네릭 함수를 호출하는 것과 동일한 타입 인수 유추 규칙을 따른다.
    • // 구현 class Factory<T> { protected products: T[]; constructor(product: T) { this.products = [product]; } create(product: T) { this.products.push(product); } getProducts() { return [...this.products]; } }; // 호출 const factory = new Factory({ name: 'KIA', description: 'car factory' }); // 확장 class CoffeeFactory<T> extends Factory<T> {} const coffeeFactory = new CoffeeFactory<{ menu: string, price: number }>({ menu: 'americano', price: 2000 });
  • 정적 클래스 제네릭
    • ex)
    • class BothLogger<OnInstance> { instanceLog(value: OnInstance) { console.log('instanceLog.value > ', value); return value; } static A: OnInstance; static staticLog<OnStatic>(value: OnStatic) { let instanceLogValue: OnInstance; console.log('staticLog.value > ', value); return value; } }
    • OnInstance 타입은 인스턴스 생성시 정해진다.
    • 인스턴스를 생성하지 않아도 접근 가능한 정적(static) 클래스 메서드에서는 클래스에 선언된 어떤 타입 매개변수에도 접근할 수 없다.
  • 메서드 제네릭
    • 인스턴스와 무관하게 메서드에서 자체 제네릭 타입 사용 가능
  • 제네릭 제한자
    • 제네릭 기본값
      • 함수 매개변수에 기본값을 제공하듯 제네릭 타입 매개변수에 기본 타입 지정 가능
    • 제한된 제네릭 타입 (extends)
      • extends 키워드를 사용해 T의 타입 제한
      • keyof는 전체 키 대상, extends keyof는 해당 프로퍼티의 특정키를 직접 검사
  • Promise
    • 최종적으로 resolve된 값을 나타내는 단일 타입 매개변수를 갖는다.
  • async 함수
    • async 함수의 반환 타입은 항상 Promise 타입이다.
  • 제네릭 명명규칙
    • 첫 번쨰 타입 인수로 T를 사용
    • 후속 타입 매개변수가 존재하면 U, V 등을 사용
    • 타입 인수가 어떻게 사용되어야 하는지 맥락과 관련된 정보가 알려진 경우 해당 용어의 첫 글자를 사용 (상태 관리 라이브러리-S, 키-K, 값-V)
    • 여러 개의 타입 매개변수를 갖거나 목적이 명확하지 않은 경우 완전한 이름을 사용