[이펙티브 타입스크립트] - 아이템31 ~ 아이템35

Lee Jeong Min·2022년 3월 14일
2

TypeScript

목록 보기
14/18
post-thumbnail

[아이템31] 타입 주변에 null 값 배치하기

strictNullChecks 속성으로 많은 오류가 표시되게 함으로써 null 값과 관련된 문제점을 찾아내 반드시 필요하다!

이 장에서 말하는 내용은 한 범위안의 변수가 null인 경우와 그렇지 않은 경우보다, 모두가 null이거나 전부 null이 아닌 경우로 분명히 구분하는것이 쉽다는 것을 말하고 있음.

→ 타입에 null을 추가하는 방식으로 모델링 할 수 있다.

const extent = (nums: number[]) => {
  let min;
  let max;
  for (const num of nums) {
    if (!min) {
      min = num;
      max = num;
    } else {
      min = Math.min(min, num);
      max = Math.max(max, num); // 이 부분에서 에러
    }
  }
  return [min, max];
};

// ---------------------------
// 아래는 단일 객체 사용 의사 코드
let result: [number, number] | null = null;
for() {
  if(!result) ...
  else
}

이 코드의 문제점으로 min 값만 체크하기 때문에 max부분에서 number | unudefined 형식의 인수가 number에 할당될 수 없다고 나온다. 또한, [0, 1, 2]의 경우 0이 falsy값이므로 값이 덮어씌워지는 경우와 nums 배열이 빈 배열이라면, 둘 다 undefined를 반환한다.

→ 문제를 해결하기 위해 단일 객체 사용하고, 함수호출의 결과값을 단언 혹은 if 구문으로 체크하면 된다.

이러한 null을 섞어서 사용하는 경우가 클래스에서 문제가 될수 있는데, 값이 완전히 준비되지 않은 상태에서 네트워크 요청을 하는 경우 그 사이의 속성들의 값이 null이냐 아니냐에 따라서 불확실성이 생긴다.

요약

  • 설계 시 한 값의 null 여부가 다른 값의 null 여부에 암시적으로 관련되도록 하면 X
  • API 작성 시 반환 타입을 큰 객체로 만들고, 타입 전체가 null이거나 null이 아니게 만들어야한다.
  • 클래스를 만들 때 필요한 모든 값이 준비되었을때 생성하여 null이 존재하지 않도록 해야함
  • strcitNullChecks 반드시 필요!

[아이템32] 유니온의 인터페이스보다는 인터페이스의 유니온을 사용하기

코드로 보는게 편하다.

// 이렇게 작성하는 것 보다
interface Layer {
  layout: FillLayout | LineLayout | PointLayout;
  paint: FillPaint | LinePaint | PointPaint;
}

// 이렇게 작성하자.(각각 타입의 계층을 분리된 인터페이스로 두기)
interface FillLayer {
  type: 'fill';
  layout: FillLayout;
  paint: FillPaint;
}

interface LineLayer {
  type: 'line'
  layout: LineLayout;
  paint: LinePaint;
}

interface PointLayer {
  type: 'paint'
  layout: PointLayout;
  paint: PointPaint;
}

type Layer = FillLayer | LineLayer | PointLayer;

이렇게 하면 잘못된 조합으로 섞이는 경우를 방지할 수 있다. 또한 안에 태그된 유니온이 있으면 태그를 참고하여 Layer의 타입의 범위를 좁힐 수도 있다.

선택적 필드가 여러개 있는 경우 두 개의 속성을 하나의 객체로 모아 그 객체가 존재하는지 체크하는 방식으로 사용하면 편리하다. 또한 API와 같이 타입의 구조를 손 댈 수 없는 상황에서 인터페이스의 유니온을 사용해 관계를 모델링할 수 있다.

→ 이 두 경우 모두 타입 정의를 통해 속성 간의 관계를 더 명확하게 만들 수 있다.

[아이템33] string 타입보다 더 구체적인 타입 사용하기

string 타입 → 범위가 넓다

// 이렇게 쓰기 보다
interface Album {
  artist: string;
  title: string;
  releaseDate: string;
  recordingType: string;
}

// 이렇게 쓰자
type RecordingType = 'live' | 'studio';

interface Album {
  artist: string;
  title: string;
  releaseDate: Date;
  recordingType: RecordingType;
}

위와 같이 바꾸면 3가지 장점

  • 타입을 명시적으로 정의함으로써 다른 곳으로 값이 전달되어도 타입 정보가 유지
  • 타입을 명시적으로 정의하고 해당 타입의 의미를 설명하는 주석을 붙여넣을 수 있다.(편집기에서 확인 가능)
  • keyof 연산자로 더욱 세밀하게 객체의 속성 체크가 가능해진다.
// 이렇게 작성하면 반환되는 타입이 (string | Date)[]가 되는데,
const pluck = <T>(recors: T[], key: keyof T) => {
  return records.map(r => r[key]);
}

// 이렇게 작성하면 반환되는 타입이 Date[] 가 된다.
// 또한 호출 부분에 있어서 매개변수 타입이 정밀해진 덕분에 자동완성 기능을 제공해준다.
const pluck = <T, K extends keyof T>(records: T[], key: K) => {
  return records.map(r => r[key]);
}

string의 부분 집합을 정의할 수 있는 기능은 자바스크립트 코드에 타입 안전성을 크게 높인다. 따라서 객체의 속성 이름을 함수 매개변수로 받을 때, string보다는 keyof T를 사용하자.

[아이템34] 부정확한 타입보다는 미완성 타입을 사용하기

타입선언의 정밀도를 높이려다가 더 좋지않은 개발 경험을 할 수 있으므로 이런 일을 할 때 주의해서 해야한다!

→ 오히려 타입이 부정확해지는 경우도 있다.

또한 타입선언의 정밀도를 높이려다가 정밀도를 높이지 않았을 때보다 발생하는 메시지의 오류가 부정확해지는 경우도 있다.

따라서 정확하게 타입을 모델링할 수 없다면 부정확하게 모델링하지 말고, anyunknown을 구별해서 사용하자.

any: 어떠한 값이든 가능. 타입을 좁혀서 사용하지 않아도 된다.

unknown: 어떠한 값이 올 수 있는지 모르므로, 타입을 좁혀서 사용함. 다른값에 할당이 불가능하다.(any 타입 제외)

아래는 참고 사이트(any vs unknown)

typescript의 unknown과 any의 차이

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

명세를 참고해 타입을 생성하는 것이 사용자가 실수를 줄일 수 있게 도와준다.

데이터를 사용해 타입을 생성하면 눈앞에 있는 데이터만 참고하기 때문에 예기치 않은 곳에서 오류가 발생할 수 있다.

예시로 Geojson을 가져왔는데, 여기서 GeometryCollection 타입인 경우 coordinates 속성이 없어 발생하는 문제를 다루며 이를 분기처리하여 에러를 잡음

타입을 직접 선언하였을 때, 이러한 예외 상황이 포함되지 않았음을 통해 명세를 기반으로 작성하는 것이 더 안정성을 높일 수 있다는것을 말함

특히 GraphQL 처럼 자체적인 타입이 정의된 API에서 잘 동작한다. (GraphQL 써보고 싶다...)

쿼리에서 타입을 생성하기 위해 GraphQL 스키마가 필요하고 Apollo를 통해 스키마를 얻는다. 이를 통해 명세로부터 타입을 가져올 수 있고, 타입과 실제 값이 항상 일치하여 좋다!

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글