Effective Typescript (6)

Jaewoong2·2022년 12월 30일
0

string 타입 보다 더 구체적인 타입 사용하기

interface Album {
  artist: string;
  title: string;
  releaseDate: string; // YYYY-MM-DD
  recordingType: string; // "live" | "studio"
}


const kindOfBlue: Album = {
  artist: "artist",
  title: "kind Of Blue",
  releaseDate: "22-03-04",
  recordingType: "LIVE"
}
  • 타입에서는 타입체커가 오류를 확인 하지 못하지만, YYYY-MM-DD 식의 형태를 처리하는 함수가 짜여 있을때, 오류가 발생 하게 된다.

  • 타입 체커가 정확히 체크 할 수 있도록 타입을 구체적으로 작성 해야한다.

type RecordingType = "live" | "studio"

interface Album {
  artist: string;
  title: string;
  releaseDate: Date; // YYYY-MM-DD
  recordingType: RecordingType; // "live" | "studio"
}


const kindOfBlue: Album = {
  artist: "artist",
  title: "kind Of Blue",
  releaseDate: "22-03-04", // 에러 발생 (Type 'string' is not assignable to type 'Date'.(2322))
  recordingType: "LIVE" // 에러 발생 (Type '"LIVE"' is not assignable to type '"live" | "studio"'. Did you mean '"live"'?(2820))
}
  • 타입체커가 에러를 찾아줘서 실수를 잡을 수 있게 된다.

keyof 연산자와 제네릭 을 통해서 매개변수의 타입을 좁히기


/** const extractObjectArrayByKey: (objs: any[], key: string) => any[] */
const extractObjectArrayByKey = (objs: any[], key: string) => {
	return objs.map(obj => obj[key])
}
  • 문제점
    • objsany[] 이기 때문에, 객체의 배열이 아닌 다른 값의 배열이 들어오게 되면 오류가 발생하게 된다
    • 또한, any[] 가 반환되면 어떤 타입인지 확인 할 수 없다
  • 해결방법
    • 제네릭 사용하기 (오류 있음)
      • obj의 타입에 string 타입의 속성이 정해져 있지 않아 인덱싱 할수 없다는 오류 발생한다.
const extractObjectArrayByKey: <T>(objs: T[], key: string) => any[] = (objs, key) => {
  return objs.map(obj => obj[key]) 
  // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'.  No index signature with a parameter of type 'string' was found on type 'unknown'.(7053)
}
  • 제네릭keyof 사용하기

// function extractObjectArrayByKey<T>(objs: T[], key: keyof T): T[keyof T][]
function extractObjectArrayByKey <T>(objs: T[], key: keyof T) {
	return objs.map(obj => obj[key]) 
}


/**
function extractObjectArrayByKey<{
    a: string;
    b: number;
    c: Date;
}>(objs: {
    a: string;
    b: number;
    c: Date;
}[], key: "a" | "b" | "c"): (string | number | Date)[]

const answer: (string | number | Date)[]
 */
const answer = extractObjectArrayByKey([{ a: "1", b: 2, c: new Date('2030-12-29') }], 'a')
  • 오류

    • 이렇게 작성 할 경우, return 값인 answer가 문자열의 배열일지 숫자의 배열인지 타입이 좁혀주지 못한다.

    • 예를들어) 문자에 대한 메소드인 answer.toUpperCase() 등에 접근을 못하게 된다

  • 반환타입 구체화 하기
    • Key 타입을 제네릭화 하면, 제네릭에 따라 반환타입이 좁혀지는 결과를 얻을 수 있다

// function extractObjectArrayByKey<T>(objs: T[], key: keyof T): T[keyof T][]
function extractObjectArrayByKey <T, K extends keyof T>(objs: T[], key: K) {
	return objs.map(obj => obj[key]) 
}


/**
function extractObjectArrayByKey<{
    a: string;
    b: number;
    c: Date;
}, "a">(objs: {
    a: string;
    b: number;
    c: Date;
}[], key: "a"): string[]

const answer: string[]
 */
const answer = extractObjectArrayByKey([{ a: "1", b: 2, c: new Date('2030-12-29') }], 'a')

const answer = extractObjectArrayByKey([{ a: "1", b: 2, c: new Date('2030-12-29') }], 'd') // 에러 발생

타입 브랜드 네임 붙이기

타입스크립트는 덕 타이핑 을 사용하기 때문에 아래와 같은 상황이 벌어진다.

type Vector2D = {
  x: number;
  y: number;
}

function calculoator(point: Vector2D, point2: Vector2D): Vector2D {
  return { x: point.x + point2.x, y: point2.y + point2.y }
}

const pointer3D = { x:2, y: 3, z: 4 }
const pointer2D = { x:2, y: 3 }

calculoator(pointer2D, pointer3D) // 오류 발생 안함

실제로 객체의 속성이 다르더라도, 덕 타이핑 을 이용하기 때문에, Vector2D 의 속성이 객체에 모두 있으면, 타입체커를 통과 하게 된다.

사소한 실수로 이러한 것들을 막기 위해서는 브랜드 네이밍을 붙이면 실수를 어느정도 방지 할 수 있다.

type Point = {
  x: number;
  y: number
}

type Vector2D = Point & { _brand: "2D" }
type Vector3D = Point & { z: number, _brand: '3D' }

function calculoator(point: Vector2D, point2: Vector2D): Vector2D {
  return { x: point.x + point2.x, y: point2.y + point2.y, _brand: "2D" }
}

const pointer2D = { x:2, y: 3 }
const pointer3D = { x:2, y: 3, z: 4 }

function vector2D(x: number, y: number): Vector2D {
  return { x, y, _brand: '2D' }
}


function vector3D(x: number, y: number, z: number): Vector3D {
  return { x, y, z, _brand: '3D' }
}

calculoator(vector2D(pointer2D.x, pointer2D.y), pointer3D(pointer3D.x, pointer3D.y, pointer3D.z)) // This expression is not callable. Type '{ x: number; y: number; z: number; }' has no call signatures.(2349)
profile
DFF (Development For Fun)

0개의 댓글