[Effective TypeScript] 4장 29-32 타입 설계

채동기·2023년 3월 7일
0

TypeScript

목록 보기
13/21

아이템 29) 사용할 때는 너그럽게, 생성할 때는 엄격하게

  • 보통 매개변수 타입은 반환 타입에 비해 범위가 넓은 경향이 있습니다. 선택 속성과 유니온 타입은 반환 타입보다 매개변수 타입에 더 일반적입니다.
  • 매개변수와 반환 타입의 재사용을 위해서 기본 형태(반환 타입)와 느슨한 형태(매개변수 타입)를 도입하는 것이 좋습니다.

아이템 30) 문서에 타입 정보를 쓰지 않기

  • 주석과 변수명에 타입 정보를 적는 것은 피해야 합니다. 타입 선언이 중복되는 것으로 끝나면 다행이지만 최악의 경우는 타입 정보에 모순이 발생할 수 있습니다.
/** nums를 변경하지 않습니다. */
function sort(nums: number[]) {
  /* ... */
} // bad!!

function sort(nums: readonly number[]) { /* ... */ } // good!!!
  • 타입이 명확하지 않는 경우는 변수명에 단위 정보를 포함하는 것을 고려하는 것이 좋습니다.(예를 들어 timeMs 또는 temperatureC)

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

  • 한 값의 null 여부가 다른 값의 null 여부에는 암시적으로 관련되도록 설계하면 안됩니다.
  • API 작성 시에는 반환 타입을 큰 객체로 만들고 반환 타입 전체가 null이거나 null이 아니게 만들어야 합니다. 사람과 타입 체커 모두에게 명료한 코드가 될 것 입니다.
  • 클래스를 만들 때는 필요한 모든 값이 준비되었을 때 생성하여 null이 존재하지 않도록 하는 것이 좋습니다.
interface UserInfo {
  name: string;
}
interface Post {
  post: string;
}
declare function fetchUser(userId: string): Promise<UserInfo>;
declare function fetchPostsForUser(userId: string): Promise<Post[]>;
class UserPosts {
  user: UserInfo | null;
  posts: Post[] | null;

  constructor() {
    this.user = null;
    this.posts = null;
  }

  async init(userId: string) {
    return Promise.all([
      async () => (this.user = await fetchUser(userId)),
      async () => (this.posts = await fetchPostsForUser(userId)),
    ]);
  }

  getUserName() {
    return this.user.name;
  }
}

위에서 네트워크가 요청되는 동안 속성값이 불확실해지기 때문에 버그가 생길수 있습니다.

interface UserInfo { name: string }
interface Post { post: string }
declare function fetchUser(userId: string): Promise<UserInfo>;
declare function fetchPostsForUser(userId: string): Promise<Post[]>;
class UserPosts {
  user: UserInfo;
  posts: Post[];

  constructor(user: UserInfo, posts: Post[]) {
    this.user = user;
    this.posts = posts;
  }

  static async init(userId: string): Promise<UserPosts> {
    const [user, posts] = await Promise.all([
      fetchUser(userId),
      fetchPostsForUser(userId)
    ]);
    return new UserPosts(user, posts);
  }

  getUserName() {
    return this.user.name;
  }
}

필요한 데이터가 모두 준비된 후에 클래스를 만들도록 바꾸면 해결할 수 있습니다.

  • strictNullChecks를 설정하면 코드에 많은 오류가 표시되겠지만, null 값과 관련된 문제점을 찾아낼 수 있기 때문에 반드시 필요합니다.

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

-유니온 타입의 속성을 여러 개 가지는 인터페이스에서는 속성 간의 관계가 분명하지 않기 때문에 실수가 자주 발생하므로 주의해야 합니다.
-유니온의 인터페이스보다 인터페이스의 유니온이 더 정확하고 타입스트립트가 이해하기도 좋습니다.

  • 타입스트립트가 제어 흐름을 분석할 수 있도록 타입에 태그를 넣는 것을 고려해야 합니다. 태그된 유니온은 타입스크립트와 매우 잘 맞기 때문에 자주 볼 수 있는 패턴입니다.
type FillPaint = unknown;
type LinePaint = unknown;
type PointPaint = unknown;
type FillLayout = unknown;
type LineLayout = unknown;
type PointLayout = unknown;
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;
function drawLayer(layer: Layer) {
  if (layer.type === 'fill') {
    const {paint} = layer;  // Type is FillPaint
    const {layout} = layer;  // Type is FillLayout
  } else if (layer.type === 'line') {
    const {paint} = layer;  // Type is LinePaint
    const {layout} = layer;  // Type is LineLayout
  } else {
    const {paint} = layer;  // Type is PointPaint
    const {layout} = layer;  // Type is PointLayout
  }
}

출처

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

profile
what doesn't kill you makes you stronger

0개의 댓글