Archive

Effective Typescript - week 4 (item 36-37)

|

Effective Typescript - week 4 (item 36-37)


해당 포스트는 [이펙티브 타입스크립트](댄 밴더캄 지음, 장원호 옮김, 인사이트, 2021) 책을 읽으며 정리한 내용입니다.

Item36. 해당 분야의 용어로 타입 이름 짓기

타입, 변수의 이름을 잘 짓는 것은 추상화의 수준과 가독성을 높일 수 있다.

interface Animal {
  name: string;
  endangered: boolean;
  habit: string;
}

const leopard: Animal = {
  name: 'Snow Leopard',
  endangered: false,
  habit: 'tundra',
};
  • name은 매우 일반적인 용어이므로 의미가 불분명하다
  • endangered는 이미 멸종된 동물일 경우 어떻게 표현할지 불분명하다
  • habit이 단순 string이라 범위가 너무 넓다
  • 객체의 명과 name이 겹쳐서 의도가 다른지 불분명하다

조금 더 명확하게 데이터를 표현하고자 한다면 다음과 같을것이다

interface Animal {
  commonName: string;
  genus: string;
  species: string;
  status: ConservationStatus;
  climates: KoppenClimate[];
}
type ConservationStatus = "EX" | "EW" | "CR";
type KoppenClimate = "Af" | "Am" | "As" | "Aw";

const snowLeopard: Animal = {
  commonName: "Snow LeoPard",
  genus: "Panthera",
  species: "Uncia",
  status: "EW",
  climates: ["As", "Aw"],
};
  • 동일한 의미를 나타낼 때에는 같은 용어를 사용해야한다
  • data, item, object 등 모호하고 의미없는 이름은 지양한다
  • 포함된 내용, 계산 방식 등이 아닌 데이터 자체가 무엇인지 고려해야한다


Item37. 공식 명칭에는 상표를 붙이기

구조적 타이핑으로 인해 오히려 잘못된 결과가 나올 수 있다.

값을 구분하기 위해 공식 명칭이 필요하다면 상표를 붙이는 방법을 고려해볼 수 있다.

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);
const oneMin = seconds(60);



참고 자료


이펙티브 타입스크립트 - 댄 밴더캄 지음

Effective Typescript - week 4 (item 33-35)

|

Effective Typescript - week 4 (item 33-35)


해당 포스트는 [이펙티브 타입스크립트](댄 밴더캄 지음, 장원호 옮김, 인사이트, 2021) 책을 읽으며 정리한 내용입니다.

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

string 타입은 범위가 매우 넓기 때문에 남발하는것은 좋지 않다.

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

위와 같이 다 string으로 타입을 잡으면 releaseDate에 잘못된 날짜형식이나 recordingType에 엉뚱한 값이 들어올 수 있다.

또, function의 매개변수에서 매개변수1, 2 모두 string일 경우 2, 1과 같이 반대로 인자가 들어와도 타입체크를 통과하게 된다.

/** 타입이 잘 작성되었는가? */
type RecordingType = 'studio' | 'live';

interface Album {
  recordingType: RecordingType;
}

function getAlbum(recordingType: RecordingType) {}

위와 같이 타입을 따로 표기하면 재사용도 되고 타입을 사용한 곳에서 마우스를 올려놓으면 주석에 써놓은 설명을 볼 수 있다.

스크린샷


또, 객체의 속성 이름을 매개변수로 받을 때에는 keyof T를 사용하는 것이 좋다.


Item34. 부정확한 타입보다는 미완성 타입을 사용하기

일반적으로 any의 사용을 줄이고 명시적으로 타입을 달아주는 것이 좋지만, 타입의 모델링이 까다로운 경우는 괜히 부정확한 타입으로 인해 생산성이 떨어지는 경우가 생길 수 있다.

오류메시지가 난해해서 읽기 어려워진다거나, 자동완성을 방해할 수도 있다.


Item35. 데이터가 아닌, API와 명세를 보고 타입 만들기

API 명세가 있다면 명세로부터 타입을 생성하는 것이 좋다. GraphQL과 상성이 좋음.

특히, 특정 쿼리에 대해 타입스크립트의 타입을 생성할 수 있다는 점이 꿀~

쿼리와 스키마가 바뀌면 타입도 자동으로 바뀐다. 타입은 GraphQL 스키마로부터 생성되기 때문에 타입과 실제 값이 항상 일치한다.



참고 자료


이펙티브 타입스크립트 - 댄 밴더캄 지음

Effective Typescript - week 4 (item 30-32)

|

Effective Typescript - week 4 (item 30-32)


해당 포스트는 [이펙티브 타입스크립트](댄 밴더캄 지음, 장원호 옮김, 인사이트, 2021) 책을 읽으며 정리한 내용입니다.

Item30. 문서에 타입 정보를 쓰지 않기

주석과 변수명에 타입 정보를 적는 것을 피한다. 타입 선언 자체만으로 이해할 수 있기 때문.

/** 매개변수 item을 변경하지 않습니다. */
function add(item: string) {} // X

// -----------------------------------
function add(item: readonly string) {} // O  

타입이 명확하지 않은 경우 변수명에 단위 정보를 기입하는 정도는 용인된다. (ex: timeMs, temperatureC …)


Item31. 타입 주변에 null값 배치하기

function extent(nums: number[]) {
  let min, max;
  for (const num of nums) {
    if (!min) {
      min = num;
      max = num;
    } else {
      min = Math.min(min, num);
      max = Math.max(max, num); // Error: 'undefined' 형식은 'number' 형식에 할당할 수 없습니다.
    }
  }
  return [min, max];
}

위 분기문에 null / undefined 체크에 max는 포함되어있지 않아서 strictNullChecks에 의해 오류가 발생했다.

이 함수를 사용하려한다면 또 오류가 발생할 것이다.

min과 max를 한 객체에 넣어 null이거나 null이 아니거나로 나누어 해결할 수 있다.

function extent(nums: number[]) {
  let result: [number, number] | null = null;
  for (const num of nums) {
    if (!result) {
      result = [num, num];
    } else {
      result = [Math.min(num, result[0]), Math.max(num, result[1])];
    }
  }
  return result;
}

이 함수는 다음과 같이 사용할 수 있다.

const [min, max] = extent([0,1,2,3])!;
const span = max - min;

//----------------------------------------

const range = extent([0,1,2]);
if (range) {
  const [min, max] = range;
  const span = max - min;
}

한 값의 null 여부가 다른 값의 null 여부에 관련되도록 설계하면 디버깅이 힘들어진다.

반환 타입을 큰 객체로 만들고 반환 타입 전체가 null이거나 null이 아닌 경우로 나누면 명료한 코드가 된다.


Item32. 유니온의 인터페이스보다 인터페이스의 유니온 사용하기

interface Layer {
  type: 'fill' | 'line' | 'point';
  layout: FillLayout | LineLayout | PointLayout;
  paint: FillPaint | LinePaint | PointPaint;
}

위와 같이 인터페이스 안에 유니온을 쓸 경우, type fill과 LineLayout을 같이 사용하는 등 실수를 유발할 수 있고 타입 추론도 어려워진다.

interface FillLayer {
  type: 'fill',
  layout: FillLayout;
  paint: FillPaint
}
interface LineLayer {
  type: 'Line',
  layout: LineLayout;
  paint: LinePaint
}
interface PointLayer {
  type: 'Point',
  layout: PointLayout;
  paint: PointPaint
}
type Layer = FillLayer | LineLayer | PointLayer;

function draw(layer: Layer) {
  if (layer.type === 'fill') {
    const { layout, paint } = layer;
  	//...
  }
}

타입스크립트가 제어의 흐름을 분석할 수 있고 코드의 정확성을 체크하는데 도움이 된다.



참고 자료


이펙티브 타입스크립트 - 댄 밴더캄 지음

Effective Typescript - week 4 (item 28-29)

|

Effective Typescript - week 4 (item 28-29)


해당 포스트는 [이펙티브 타입스크립트](댄 밴더캄 지음, 장원호 옮김, 인사이트, 2021) 책을 읽으며 정리한 내용입니다.

Item28. 유효한 상태만 표현하는 타입을 지향하기

interface State {
  pageText: string;
  isLoadingL boolean;
  error?: string;
}

function renderPage(state: State) {
  if (state.error) {
    return 'error...';
  } else if (state.isLoading) {
    return 'Loading...';
  }
  return state.pageText;
}

위와 같은 코드가 있을때, 분기 조건이 명확하지 않아서 버그를 유발할 수 있다.

isLoading이 true면서, error 값이 설정되는 무효한 상태를 허용하기 때문이다.

interface RequestPending {
  state: 'pending';
}
interface RequestError {
  state: 'error';
  error: string;
}
interface RequestSuccess {
  state: 'success';
  pageText: string;
}
type RequestState = RequestPending | RequestError | RequestSuccess;

interface State {
  currentPage: string;
  requests: { [page: string]: RequestState };
}

function renderPage(state: State) {
  const { currentPage } = state;
  const requestState = state.requests[currentPage];
  switch (requestState.state) {
    case 'pending':
      return 'Loading...';
    case 'error':
      return 'error...';
    case 'success':
      return requestState.pageText;
  }
}

위처럼 무효한 상태를 허용하지 않도록 각각의 상태를 명시적으로 모델링하는 태그된 유니온을 사용할 수 있다.


Item29. 사용할 때는 너그럽게, 생성할 때는 엄격하게

함수의 매개변수는 타입의 범위가 넓어도 되지만, 결과를 반환할 때는 타입의 범위가 구체적이어야 한다.

선택적 속성과 유니온 타입은 반환 타입보다는 매개변수의 타입으로 더 많이 사용된다.

매개변수와 반환 타입의 재사용을 위해 기본 형태(반환 타입)와 느슨한 형태(매개변수 타입)을 도입하는 것이 좋다.



참고 자료


이펙티브 타입스크립트 - 댄 밴더캄 지음