Typescript 좀 더 깊숙히

Harry·2020년 12월 10일
1

Typescript

목록 보기
1/1

_(언더스코어)사용

  • 1_000_234 → 가독성을 높임

철저한 속성 초기화

class Library {
  titles: string[]
  constructor() {}
}
const library = new Library()
  • 속성을 제대로 초기화하지 않은 상태에서 Library.titles 를 가져다 쓰려고 하면 에러 발생
  • strictPropertyInitialization 옵션 존재
    • 해당 옵션 활성화를 하려면 → strictNullCheck 도 같이 활성화되어 있어야 한다.
  • 위의 옵션을 활성화했다면 컴파일러가 에러를 표시할 것 → 기본값을 입력하지 않았기 때문 → 기본값을 입력하지 않을 것이라면 titles?: string[] 로 변경할 것.
  • 의존성 주입등으로 인해 런타임에서 titles 가 결정되고 → 컴파일러가 이 사실을 신경쓸 필요가 없다면 → titles!: string[] 로 표현할 것 ( 이 속성은 지금 신경쓰지 않아도 된다. )

Union 타입에서의 타입가드 사용 → in 사용

interface Admin {
  id: string;
  role: string;
}
interface User {
  id: string;
  email: string;
}
function redirect(user: Admin | User) {
  if (/* 사용자가 어드민이라면 */) {
  }
}
  • 위의 타입을 사용하고 있을 때 함수 내에서 타입을 어떻게 나눠줄까?
  • in 연산자를 이용하여 타입을 나눠주자
function redirect(user: Admin | User) {
  if ('role' in user) {
    routeToAdminPage(user.role) // 이 부분에서 자동완성 할 때는 `role` 속성밖에 나오지 않음
  } else {
    routeToHomePage(user.email) // 여기서는 `email` 만 나옴
  }
}
  • user 라는 객체 내부에 role 변수가 존재한다면 내부 로직에서 user 에 대한 타입은 Admin 타입으로 추론된다.

Mapped Type 의 활용

readonly 속성 추가

interface Pet {
  name: string
  age: number
}
type ReadonlyPet = { readonly [K in keyof Pet]: Pet[K] }
const pet: Pet = { name: 'Happy', age: 10 }
const readonlyPet: ReadonlyPet = { name: 'Cerberus', age: 1000 }
pet.age = 15
readonlyPet.age = 200 // readonly 속성을 수정하려고 하기 때문에 에러

Pet에 옵셔널 속성을 추가하고, 해당 옵셔널 속성을 필수로 변경하는 방법

interface Pet {
  name: string
  age: number
  **favoritePark?: string**
}
type ReadonlyPet = { readonly [K in keyof Pet]**-?**: Pet[K] } // `-?` 부분 주목
// 이제 favoritePark 속성이 없다고 에러가 난다.
const readonlyPet: ReadonlyPet = { name: 'Cerberus', age: 1000 }
  • Pet에서는 favoritePark 이 옵셔널 속성이지만 ReadonlyPet 에서는 모든 속성이 필수로 있어야 한다.

타입과 인터페이스의 차이

  • type은 같은 파일 안에서 두 번 선언될 수 없다.
  • interface 는 중복 선언될 경우 타입 결합과 동일하게 동작

조건부 타입 활용 → 타입을 동적으로 할당하기

T extends U ? X : Y
  • 위의 코드가 조건부 타입
  • T 라는 타입이 U 타입과 부합한다면 타입은 X 가 되고 아니면 Y 가 된다.

Example)

type NonNullable<T> = T extends null | undefined ? never : T;
  • 위의 타입에서 T 라는 제네릭에 타입이 null 혹은 undefined 라면 NonNullable 의 타입은 never 가 된다.
    • 아니라면 T
  • 조건에 맞지 않는 타입은 never 을 활용 → never 타입이 유니언 타입 안에 있으면 자동으로 무시된다.

Example-2)

type ArrayOnly<T> = T extends any[] ? T : never
type StringOrNumbers = ArrayOnly<string | number | string[] | number[]>
  • 위의 stringOrNumbers 의 타입은 string[] OR number[]

Example-3)

interface ItemService {
  getItem<T extends string | number>(id: T): T extends string ? Book : Tv
}
let itemService: IItemService
const book = itemService.getItem('10')
const tv = itemService.getItem(10)
const wrong = itemService.getItem(false) // 에러

제네릭 함수 타입이 어떤 타입으로 리턴되는지 추측

function generateId(seed: number) { return seed + 5 }
function lookupEntity(id: number) { ... }
lookupEntity(generateId(10)) // success
  • 위의 코드에서 generateId 함수 리턴 타입은 자동으로 추론이 되어 number 이 된다.
  • generateId 함수의 리턴 값을 lookupEntity 의 인자로 넘겨준다.
  • 하지만 lookupEntity 의 인자로 타입을 string 으로 바꾸고 싶다면?
    • generateId 함수의 파라미터 타입도 변경해주어야 한다.
    • 이렇게 수동적으로 일일이 변경해주어야 하나..?

infer 키워드를 사용할 것.

  • "해당 타입을 추론하라" 라고 타입스크립트 엔진에게 시키는 것

  • typescript 에서 기본으로 제공해주는 ReturnType 타입을 예시로 들어보자

    type ReturnType<F> = F extends (...args: any[]) => infer R ? R : any
    • 함수 타입의 F 의 리턴 타입을 추론할 수 있다면 추론된 R 이라는 타입을 리턴하고
    • 할 수 없다면 any 를 리턴한다.

Example)

type UnpackPromiseArray<P> = P extends Promise<infer K>[] ? K : any
const arr = [Promise.resolve(true)]
type ExpectedBoolean = UnpackPromiseArray<typeof arr> // -> boolean
  • 변수의 타입이 Promise<infer K>[] 라면 K 타입을 리턴한다.
  • 타입스크립트 엔진이 해당 Promise 의 리턴 타입을 추론한다. ( infer K )

Mapped Types Conditional Types

type NonNullablePropertyKeys<T> = {
  [P in keyof T]: null extends T[P] ? never : P
}[keyof T];

type User = {
  name: string;
  email: string | null;
};

type NonNullableUserPropertyKeys = NonNullablePropertyKeys<User>;
  • [P in keyof T] → name, email
  • null extends T[P] ? never : P
    • name → null extends string ? never : P → P
    • email → null extends string | null ? never : P → never
  • never 타입 속성을 가진 key는 interface 에서 제거됨
  • a의 값은 "name" 만 가능

Process ( T : User )

  1. T[P] → User[P]

    type NonNullableUserPropertyKeys = {
      [P in keyof User]: null extends User[P] ? never : P
    }[keyof User];
  2. keyof User → "name" | "email"

    type NonNullableUserPropertyKeys = {
      [P in "name" | "email"]: null extends User[P] ? never : P
    }[keyof User];
  3. unroll Union 타입

    type NonNullableUserPropertyKeys = {
      name: null extends User["name"] ? never : "name";
      email: null extends User["email"] ? never : "email";
    }[keyof User];
  4. User Type 내부 키값의 type

    type NonNullableUserPropertyKeys = {
      name: null extends string ? never : "name";
      email: null extends string | null ? never : "email";
    }[keyof User];
  5. 조건부 타입

    type NonNullableUserPropertyKeys = {
      name: "name";
      email: never;
    }[keyof User];
    //
    type NonNullableUserPropertyKeys = {
      name: "name";
      email: never;
    }["name" | "email"];
  6. name, email 에 대한 타입을 찾기 위한 indexed access를 갖고 있음
    각 타입을 개별적으로 조회하면서 해결한다
    아래아 유니온타입으로의 결과물

    ```tsx
    type NonNullableUserPropertyKeys =
      | { name: "name"; email: never }["name"]
      | { name: "name"; email: never }["email"];
    ```
  7. unroll

    type NonNullableUserPropertyKeys =
      | "name"
      | never;
  8. 결과물

    ```tsx
    type NonNullableUserPropertyKeys = "name";
    ```
    - `never` 타입은 제거한다.

    출처 - 1
    https://rinae.dev/posts/practical-advanced-typescript-summary?fbclid=IwAR0tCClWkoY1zOG0ZiGONE_YlkQztmI03oQpOv2g1JwMVv9ki-PBBQf_FLA#numeric-separator%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%ED%81%B0-%EC%88%98%EB%A5%BC-%EB%8B%A4%EB%A3%A8%EA%B8%B0
    출처 - 2
    https://mariusschulz.com/blog/conditional-types-in-typescript

0개의 댓글