[Effective TypeScript] 4장 35-37 타입 설계

채동기·2023년 3월 15일
0

TypeScript

목록 보기
15/21

아이템 35) 데이터가 아닌, API와 명세를 보고 타입 만들기

  • 코드의 구석 구석까지 타입 안정성을 얻기 위해 API 또는 데이터 형식에 대한 타입 생성을 고려해야 합니다.
  • 데이터에 드러나지 않는 예외적인 경우들이 문제가 될 수 있기 때문에 데이터보다는 명세로부터 코드를 생성하는 것이 좋습니다.
interface BoundingBox {
  lat: [number, number];
  lng: [number, number];
}
import {Feature, Geometry} from 'geojson'; //geojson에서 명세를 받아온다.
declare let f: Feature;
function helper(coordinates: any[]) {}
const geometryHelper = (g: Geometry) => {
  if (geometry.type === 'GeometryCollection') {
    geometry.geometries.forEach(geometryHelper);
  } else {
    helper(geometry.coordinates);  // OK
  }
}

const {geometry} = f;
if (geometry) {
  geometryHelper(geometry);
}

graphql를 사용할 때 apollo를 통해서 스키마를 얻을 수 있습니다.

query getLicense($owner:String!, $name:String!){
	repository(owner:$owner, name:$name) {
    	description
        licenseInfo{
        	spdxId
            name
         }
     }
 }

위를 바꾸면 밑의 예시처럼 나옵니다.

export interface getLicense_repository_licenseInfo {
  __typename: "License";
  /** Short identifier specified by <https://spdx.org/licenses> */
  spdxId: string | null;
  /** The license full name specified by <https://spdx.org/licenses> */
  name: string;
}

export interface getLicense_repository {
  __typename: "Repository";
  /** The description of the repository. */
  description: string | null;
  /** The license associated with the repository */
  licenseInfo: getLicense_repository_licenseInfo | null;
}

export interface getLicense {
  /** Lookup a given repository by the owner and repository name. */
  repository: getLicense_repository | null;
}

export interface getLicenseVariables {
  owner: string;
  name: string;
}

아이템 36) 해당 분야의 용어로 타입 이름 짓기

  • 가독성을 높이고, 추상화 수준을 올리기 위해서 해당 분야의 용어를 사용해야 합니다.
  • 같은의미에 다른 이름을 붙이면 안 됩니다. 특별한 의미가 있을 때만 용어를 구분해야 합니다.

밑의 예시처럼 명확하게 표현해야 합니다.

interface Animal {
  commonName: string;
  genus: string;
  species: string;
  status: ConservationStatus;
  climates: KoppenClimate[];
}
type ConservationStatus = 'EX' | 'EW' | 'CR' | 'EN' | 'VU' | 'NT' | 'LC';
type KoppenClimate = |
  'Af' | 'Am' | 'As' | 'Aw' |
  'BSh' | 'BSk' | 'BWh' | 'BWk' |
  'Cfa' | 'Cfb' | 'Cfc' | 'Csa' | 'Csb' | 'Csc' | 'Cwa' | 'Cwb' | 'Cwc' |
  'Dfa' | 'Dfb' | 'Dfc' | 'Dfd' |
  'Dsa' | 'Dsb' | 'Dsc' | 'Dwa' | 'Dwb' | 'Dwc' | 'Dwd' |
  'EF' | 'ET';
const snowLeopard: Animal = {
  commonName: 'Snow Leopard',
  genus: 'Panthera',
  species: 'Uncia',
  status: 'VU',  // vulnerable
  climates: ['ET', 'EF', 'Dfd'],  // alpine or subalpine
};

아이템 37) 공식 명칭에는 상표(brand)를 붙이기

  • 타입스트립트는 구조적 타이핑(덕 타이핑)을 사용하기 때문에, 값을 세밀하게 구분하지 못하는 경우가 있습니다. 값을 구분하기 위해 공식 명칭이 필요하다면 상표(brand)를 붙이는 것을 고려해야 합니다.

  • 상표(brand) 기법은 타입 시스템에서 동작하지만 런타임에 상표(brand)를 검사하는 것과 동일한 효과를 얻을 수 있습니다.

type SortedList<T> = T[] & {_brand: 'sorted'};

function isSorted<T>(xs: T[]): xs is SortedList<T> {
  for (let i = 1; i < xs.length; i++) {
    if (xs[i] > xs[i - 1]) {
      return false;
    }
  }
  return true;
}

function binarySearch<T>(xs: SortedList<T>, x: T): boolean {
  // COMPRESS
  return true;
  // END
}

number type에서도 사용가능하지만 산술연산 후에 상표가 사라지기 때문에 실제로 사용하기에는 무리가 있습니다.

type Meters = number & {_brand: 'meters'};
type Seconds = number & {_brand: 'seconds'};

const meters = (m: number) => m as Meters;
const seconds = (s: number) => s as Seconds;

const oneKm = meters(1000);  // Type is Meters
const oneMin = seconds(60);  // Type is Seconds
const tenKm = oneKm * 10;  // Type is number
const v = oneKm / oneMin;  // Type is number

출처

<이펙티브 타입스크립트> (댄 밴더캅 지음, 장원호 옮김, 인사이트, 2021)

profile
what doesn't kill you makes you stronger

0개의 댓글