# ๐Ÿ“„ Item29 ~ 33


# Item29. ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋„ˆ๊ทธ๋Ÿฝ๊ฒŒ, ์ƒ์„ฑํ•  ๋•Œ๋Š” ์—„๊ฒฉํ•˜๊ฒŒ

  • ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ํƒ€์ž…์˜ ๋ฒ”์œ„๊ฐ€ ๋„“์–ด๋„ ๋˜์ง€๋งŒ, ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํƒ€์ž…์˜ ๋ฒ”์œ„๊ฐ€ ๋” ๊ตฌ์ฒด์ ์ด์–ด์•ผ ํ•œ๋‹ค.

  • ์˜ˆ์‹œ) 3D ๋งคํ•‘ API - ์นด๋ฉ”๋ผ์˜ ์œ„์น˜๋ฅผ ์ง€์ •ํ•˜๊ณ  ๊ฒฝ๊ณ„ ๋ฐ•์Šค์˜ ๋ทฐํฌํŠธ๋ฅผ ๊ณ„์‚ฐ

# โญ๏ธ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋„ˆ๊ทธ๋Ÿฝ๊ฒŒ

declare function setCamera(camera: CameraOptions): void;
declare function viewportForBounds(bounds: LngLatBounds): CameraOptions;
  • ์ผ๋ถ€ ๊ฐ’์€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์œผ๋ฉด์„œ ๋™์‹œ์— ๋‹ค๋ฅธ ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ CameraOptions์˜ ํ•„๋“œ๋Š” ๋ชจ๋‘ ์„ ํƒ์ ์ด๋‹ค.
  • ๊ฒŒ๋‹ค๊ฐ€ LngLat ํƒ€์ž…๋„ ๋‹ค์–‘ํ•œ ํ˜•ํ…Œ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ํƒ€์ž…์„ ์ •์˜ํ•˜์—ฌ ํŽธ์˜์„ฑ์„ ์ œ๊ณตํ•˜์—ฌ ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.
interface CameraOptions {
  center?: LngLat;
  zoom?: number;
  bearing?: number;
  pitch?: number;
}

type LngLat = { lgn: number; lat: number; } | { lon: number; lat: number; } | [number, number];
  • ๋˜ํ•œ viewportForBounds ํ•จ์ˆ˜๋Š” ์œ„์™€ ๊ฐ™์€ ์ž์œ ๋กœ์šด ํƒ€์ž…์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค.
    • LngLat ํƒ€์ž…๊ณผ ์กฐํ•ฉํ•˜๋ฉด LngLatBounds์˜ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋Š” 19๊ฐ€์ง€ ์ด์ƒ์œผ๋กœ ๋งค์šฐ ์ž์œ ๋กœ์šด ํƒ€์ž…์ด๋‹ค.
type LngLatBounds = { northeast: LngLat, southwest: LngLat } | [LngLat, LngLat] | [number, number, number, number];

# โญ๏ธ ์ƒ์„ฑํ•  ๋•Œ๋Š” ์—„๊ฒฉํ•˜๊ฒŒ

function focusOndFeature(f: Feature) {
  const bounds = calculateBoundingBox(f);
  const camera = viewportForBounds(bounds);
  setCamera(camera);
  const { center: { lat, lng }, zoom } = camera;
  // ~~~ ... ํ˜•์‹์— 'lat' ์†์„ฑ์ด ์—†์Šต๋‹ˆ๋‹ค.
  // ~~~ ... ํ˜•์‹์— 'lng' ์†์„ฑ์ด ์—†์Šต๋‹ˆ๋‹ค.
  zoom; // ํƒ€์ž…์ด number | undefined
  window.location.search = `?v=@${lat},${lng}z${zoom}`;
}
  • ์œ„ ์˜ค๋ฅ˜๋Š” lat, lng ์†์„ฑ์ด ์—†๊ณ  zoom ์†์„ฑ๋งŒ ์กด์žฌํ•˜์—ฌ ๋ฐœ์ƒํ–ˆ๊ณ , zoom์˜ ํƒ€์ž…์ด number | undefined๋กœ ์ถ”๋ก ๋˜๋Š” ๊ฒƒ๋„ ์—ญ์‹œ ๋ฌธ์ œ๋‹ค.

  • ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๋Š” viewportForBounds์˜ ํƒ€์ž… ์„ ์–ธ์ด ์‚ฌ์šฉ๋  ๋•Œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋งŒ๋“ค์–ด์งˆ ๋•Œ๋„ ๋„ˆ๋ฌด ์ž์œ ๋กญ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

  • camera ๊ฐ’์„ ์•ˆ์ „ํ•œ ํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ ์œ ๋‹ˆ์˜จ ํƒ€์ž…์˜ ๊ฐ ์š”์†Œ๋ณ„๋กœ ์ฝ”๋“œ๋ฅผ ๋ถ„๊ธฐํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  • ์œ ๋‹ˆ์˜จ ํƒ€์ž…์˜ ์š”์†Œ๋ณ„ ๋ถ„๊ธฐ๋ฅผ ์œ„ํ•œ ๋ฐฉ๋ฒ•์€ ์ขŒํ‘œ๋ฅผ ์œ„ํ•œ ๊ธฐ๋ณธ ํ˜•์‹์„ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

    • ๋ฐฐ์—ด๊ณผ ๋ฐฐ์—ด ๊ฐ™์€ ๊ฒƒ์˜ ๊ตฌ๋ถ„์„ ์œ„ํ•ด LngLat์™€ LngLatLike๋ฅผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๋˜ํ•œ setCamera ํ•จ์ˆ˜๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก, ์™„์ „ํ•˜๊ฒŒ ์ •์˜๋œ Camera ํƒ€์ž…๊ณผ Camera ํƒ€์ž…์ด ๋ถ€๋ถ„์ ์œผ๋กœ ์ •์˜๋œ ๋ฒ„์ „์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
interface LngLat {
  lng: number;
  lat: number;
}

type LngLatLike = LngLat | { lon: number; lat: number; } | [number, number];

interface Camera {
  center: LngLat;
  zoom: number;
  bearing: number;
  pitch: number;
}

interface CameraOptions extends Omit<Partial<Camera>, 'center'> {
  center?: LngLatLike;
}

type LngLatBounds = { northeast: LngLatLike, southwest: LngLatLike } | [LngLatLike, LngLatLike] | [number, number, number, number];

declare function setCamera(camera: CameraOptions): void;
declare function viewportForBounds(bounds: LngLatBounds): Camera;

# Item30. ๋ฌธ์„œ์— ํƒ€์ž… ์ •๋ณด๋ฅผ ์“ฐ์ง€ ์•Š๊ธฐ

  • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ํƒ€์ž… ๊ตฌ๋ฌธ ์‹œ์Šคํ…œ์€ ๊ฐ„๊ฒฐํ•˜๊ณ , ๊ตฌ์ฒด์ ์ด๋ฉฐ, ์‰ฝ๊ฒŒ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์–ด์žˆ๋‹ค.
    • ํ•จ์ˆ˜์˜ ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ์˜ ํƒ€์ž…์„ ์ฝ”๋“œ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ฃผ์„๋ณด๋‹ค ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์ด๋ผ๋Š” ๊ฒƒ์€ ์ž๋ช…ํ•˜๋‹ค.
  • ๋ˆ„๊ตฐ๊ฐ€ ๊ฐ•์ œํ•˜์ง€ ์•Š๋Š” ์ด์ƒ ์ฃผ์„ใ…‡์€ ์ฝ”๋“œ์™€ ๋™๊ธฐํ™”๋˜์ง€ ์•Š๋Š”๋‹ค.
    • ์ฃผ์„ ๋Œ€์‹  ํƒ€์ž… ์ •๋ณด๋ฅผ ์ž‘์„ฑํ•œ๋‹ค๋ฉด ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค ํ•˜๋”๋ผ๋„ ์ •๋ณด๊ฐ€ ์ •ํ™•ํžˆ ๋™๊ธฐํ™”๋œ๋‹ค.
  • ํŠน์ • ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ค๋ช…ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด JSDoc์˜ @param ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
  • ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ์„ค๋ช…ํ•˜๋Š” ์ฃผ์„๋„ ์ข‹์ง€ ์•Š๋‹ค.
    • ๋˜ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅด ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ฃผ์„๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
    • ๊ทธ ๋Œ€์‹ , readonly๋กœ ์„ ์–ธํ•˜์—ฌ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๊ทœ์น™์„ ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋ฉด ๋œ๋‹ค.
  • ๋ณ€์ˆ˜๋ช…์— ํƒ€์ž… ์ •๋ณด๋ฅผ ๋„ฃ์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด ๋ณ€์ˆ˜๋ช… ageNum์œผ๋กœ ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค๋Š” age๋กœ ํ•˜๊ณ , ๊ทธ ํƒ€์ž…์ด number์ž„์„ ๋ช…์‹œํ•˜๋Š”๊ฒŒ ์ข‹๋‹ค.
    • ๊ทธ๋Ÿฌ๋‚˜ ๋‹จ์œ„๊ฐ€ ์žˆ๋Š” ์ˆซ์ž๋“ค์€ ์˜ˆ์™ธ์ด๋‹ค. ๋‹จ์œ„๊ฐ€ ๋ฌด์—‡์ธ์ง€ ํ™•์‹คํ•˜์ง€ ์•Š๋‹ค๋ฉด ๋ณ€์ˆ˜๋ช… ๋˜๋Š” ์†์„ฑ ์ด๋ฆ„์— ๋‹จ์œ„๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ฃผ์„๊ณผ ๋ณ€์ˆ˜๋ช…์— ํƒ€์ž… ์ •๋ณด๋ฅผ ์ ๋Š” ๊ฒƒ์€ ํ”ผํ•ด์•ผ ํ•œ๋‹ค.
    • ํƒ€์ž… ์„ ์–ธ์ด ์ค‘๋ณต๋˜๋Š” ๊ฒƒ์œผ๋กœ ๋๋‚˜๋ฉด ๋‹คํ–‰์ด์ง€๋งŒ ์ตœ์•…์˜ ๊ฒฝ์šฐ๋Š” ํƒ€์ž… ์ •๋ณด์— ๋ชจ์ˆœ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.
  • ํƒ€์ž…์ด ๋ช…ํ™•ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ๋Š” ๋ณ€์ˆ˜๋ช…์— ๋‹จ์œ„ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. (์˜ˆ๋ฅผ ๋“œ๋Ÿฌ timeMs ๋˜๋Š” temperatureC)


# Item31. ํƒ€์ž… ์ฃผ๋ณ€์— null ๊ฐ’ ๋ฐฐ์น˜ํ•˜๊ธฐ

# โญ๏ธ ์˜ˆ์‹œ1 . ์ˆซ์ž๋“ค์˜ ์ตœ์†Ÿ๊ฐ’๊ณผ ์ตœ๋Œ“๊ฐ’ ๊ณ„์‚ฐ

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;
}
// 1) non-null assertion
const [min, max] = extent([0, 1, 2])!;
const span = max - min; // ์ •์ƒ
// 2) if ๊ตฌ๋ฌธ์œผ๋กœ null check
const range = extent([0, 1, 2]);

if (range) {
  const [min, max] = range;
  const span = max - min; // ์ •์ƒ
}

# โญ๏ธ ์˜ˆ์‹œ2. ์‚ฌ์šฉ์ž์˜ ํฌ๋Ÿผ ๊ฒŒ์‹œ๊ธ€์„ ๋‚˜ํƒ€๋‚ด๋Š” ํด๋ž˜์Šค

  • ์•„๋ž˜์™€ ๊ฐ™์ด init ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด null์˜ ์กด์žฌ ์œ ๋ฌด์— ๋”ฐ๋ผ ๋„ค ๊ฐ€์ง€ ์ƒํƒœ๊ฐ€ ์กด์žฌํ•˜์—ฌ null ์ฒดํฌ๊ฐ€ ๋‚œ๋ฌดํ•˜๊ณ  ๋ฒ„๊ทธ๋ฅผ ์–‘์‚ฐํ•˜๊ฒŒ ๋œ๋‹ค.
class UserPosts {
  user: UserInfo | null;
  posts: Post[] | null;
    
  constructor() {
    this.user = null;
    this.posts = null;
  }
    
  async init(userId: string) {
    return Promise.all([
      async () => this.users = await fetchUser(userId),
      async () => this.posts = await fetchPostsForUser(userId)
    ]);
  }
    
  getUserName() {}
}
  • ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ชจ๋‘ ์ค€๋น„๋œ ํ›„์— ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๋„๋ก ๋ฐ”๊ฟ”๋ณด์ž.
class UserPosts {
  user: UserInfo;
  posts: Post[];
    
  constructor(user: UserInfo, posts: Posts[]) {
    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;
  }
}

# ๐Ÿ’ก ๊ฒฐ๋ก 

  • ํ•œ ๊ฐ’์˜ null ์—ฌ๋ถ€๊ฐ€ ๋‹ค๋ฅธ ๊ฐ’์˜ null ์—ฌ๋ถ€์— ์•”์‹œ์ ์œผ๋กœ ๊ด€๋ จ๋˜๋„๋ก ์„ค๊ณ„ํ•˜๋ฉด ์•ˆ ๋œ๋‹ค.
  • API ์ž‘์„ฑ ์‹œ์—๋Š” ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ํฐ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค๊ณ  ๋ฐ˜ํ™˜ ํƒ€์ž… ์ „์ฒด๊ฐ€ null ์ด๊ฑฐ๋‚˜ null์ด ์•„๋‹ˆ๊ฒŒ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.
  • ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค ๋•Œ๋Š” ํ•„์š”ํ•œ ๋ชจ๋“  ๊ฐ’์ด ์ค€๋น„๋˜์—ˆ์„ ๋•Œ ์ƒ์„ฑํ•˜์—ฌ null์ด ์กด์žฌํ•˜์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
  • strictNullChecks ๋ฅผ ์„ค์ •ํ•˜๋ฉด ์ฝ”๋“œ์— ๋งŽ์€ ์˜ค๋ฅ˜๊ฐ€ ํ‘œ์‹œ๋˜๊ฒ ์ง€๋งŒ, null ๊ฐ’๊ณผ ๊ด€๋ จ๋œ ๋ฌธ์ œ์ ์„ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๋‹ค.


# Item32. ์œ ๋‹ˆ์˜จ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ณด๋‹ค๋Š” ์ธํ„ฐํŽ˜์ด์Šค์˜ ์œ ๋‹ˆ์˜จ์„ ์‚ฌ์šฉํ•˜๊ธฐ

  • ํƒœ๊ทธ๋œ ์œ ๋‹ˆ์˜จ์—์„œ ์ธํ„ฐํŽ˜์ด์Šค์˜ ์œ ๋‹ˆ์˜จ ํŒจํ„ด ์ ์šฉํ•˜๊ธฐ
interface FillLayer {
  type: 'fill';
  layout: FillLayout;
  paint: FillPaint;
}

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

type Layer = FillLayer | LineLayer;
  • type ์†์„ฑ์€ 'ํƒœ๊ทธ' ์ด๋ฉฐ ๋Ÿฐํƒ€์ž„์— ์–ด๋–ค ํƒ€์ž…์˜ Layer๊ฐ€ ์‚ฌ์šฉ๋˜๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” ๋ฐ ์“ฐ์ธ๋‹ค.
    • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ํƒœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ Layer์˜ ํƒ€์ž…์˜ ๋ฒ”์œ„๋ฅผ ์ขํž ์ˆ˜๋„ ์žˆ๋‹ค.
function drawLayer(layer: Layer) {
  if (layer.type === 'fill') {
    const { paint } = layer; // ํƒ€์ž…์ด FillPaint
    const { layout } = layer; // ํƒ€์ž…์ด FillLayout
  } else {
    const { paint } = layer; // ํƒ€์ž…์ด LinePaint
    const { layout } = layer; // ํƒ€์ž…์ด LineLayout
  }
}
  • ํƒ€์ž…์˜ ๊ตฌ์กฐ๋ฅผ ์† ๋Œˆ ์ˆ˜ ์—†๋Š” ์ƒํ™ฉ์—์„œ๋„ ์ด ๊ธฐ๋ฒ•์„ ์ด์šฉํ•ด์„œ ์†์„ฑ ์‚ฌ์ด์˜ ๊ด€๊ณ„๋ฅผ ๋ชจ๋ธ๋งํ•  ์ˆ˜ ์žˆ๋‹ค.
interface Name {
  name: string;
}

interface PersonWithBirth extends Name {
  placeOfBirth: string;
  dateOfBirth: Date;
}

type Person = Name | PersonWithBirth;

function eulogize(p: Person) {
  if ('placeOfBirth' of p) {
    p; // ํƒ€์ž…์ด PersonWithBirth
    const { dateOfBirth } = p; // ์ •์ƒ, ํƒ€์ž…์ด Date
  }
}
  • ๋˜๋Š” ๋‘ ๊ฐœ์˜ ์˜ต์…”๋„ํ•œ ์†์„ฑ์„ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ๋ชจ์•„์„œ ์„ค๊ณ„ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
interface Person {
  name: string;
  birth?: {
    place: string;
    date: Date;s
  }
}

# Item33. string ํƒ€์ž…๋ณด๋‹ค ๋” ๊ตฌ์ฒด์ ์ธ ํƒ€์ž… ์‚ฌ์šฉํ•˜๊ธฐ

type RecordingType = 'studio' | 'live';

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

const kindOfBlue: Album = {
  artist: 'Miles Davis',
  title: 'Kind of Blue',
  releaseDate: new Date('1959-08-17'),
  recordingType: 'Studio', // '"Studio"' ํ˜•์‹์€ 'RecordingType' ํ˜•์‹์— ํ• ๋‹นํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
}
  • string ํƒ€์ž…๋ณด๋‹ค ๋” ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ์˜ ์žฅ์ 
    • ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜ํ•จ์œผ๋กœ์จ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ๊ฐ’์ด ์ „๋‹ฌ๋˜์–ด๋„ ํƒ€์ž… ์ •๋ณด๊ฐ€ ์œ ์ง€๋œ๋‹ค.
    • ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜ํ•˜๊ณ  ํ•ด๋‹น ํƒ€์ž…์˜ ์˜๋ฏธ๋ฅผ ์„ค๋ช…ํ•˜๋Š” ์ฃผ์„์„ ๋ถ™์—ฌ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค.
    • keyof ์—ฐ์‚ฐ์ž๋กœ ๋”์šฑ ์„ธ๋ฐ€ํ•˜๊ฒŒ ๊ฐ์ฒด์˜ ์†์„ฑ ์ฒดํฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

# โญ๏ธ keyof ์—ฐ์‚ฐ์ž

  • ์šฐ์„  ์˜ˆ์‹œ๋กœ underscore์˜ pluck ํ•จ์ˆ˜๋ฅผ ์‚ดํŽด๋ณด์ž
function pluck(records: any[], key: string): any[] {
  return records.map(r => r[key]);
}
  • ํƒ€์ž… ์ฒดํฌ๋Š” ๋˜๋‚˜ any ํƒ€์ž…์œผ๋กœ ์ธํ•ด ์ •๋ฐ€ํ•˜์ง€ ๋ชปํ•˜๋‹ค. ๊ทธ๋ž˜์„œ ์ฒซ ๋ฒˆ์งธ ๋‹จ๊ณ„๋กœ ์ œ๋„ค๋ฆญ์„ ์ด์šฉํ•˜์—ฌ ํƒ€์ž… ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๊ฐœ์„ ํ•ด๋ณด์ž.
function pluck<T>(records: T[], key: string): any[] {
  return records.map(r => r[key]); // ~~~ '{}' ํ˜•์‹์— ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ์—†์œผ๋ฏ€๋กœ ์š”์†Œ์— ์•”์‹์ ์œผ๋กœ 'any' ํ˜•์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค.
}
  • key์˜ ํƒ€์ž…์ด string ์ด์—ฌ์„œ ๋ฒ”์œ„๊ฐ€ ๋„“๋‹ค๋Š” ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.
  • ๊ทธ๋ž˜์„œ keyof ์—ฐ์‚ฐ์ž๋ฅผ ์ด์šฉํ•ด์„œ ์œ ํšจํ•œ ๊ฐ’์„ ์ง€์ •ํ•ด๋ณด์ž.
type K = keyof Album; // ํƒ€์ž…์ด "artist" | "title" | "releaseDate" | "recordingType"

// T[keyof T]๋Š” T ๊ฐ์ฒด ๋‚ด์˜ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๊ฐ’์˜ ํƒ€์ž…์ด๋‹ค.ใ„ด
function pluck<T>(records: T[], key: keyof T): T[keyof T][] {
  return records.map(r => r[key]);
}
  • ํ•˜์ง€๋งŒ ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฝ์šฐ key์˜ ๊ฐ’์œผ๋กœ ํ•˜๋‚˜์˜ ๋ฌธ์ž์—ด์„ ๋„ฃ๊ฒŒ ๋˜๋ฉด, ๊ทธ ๋ฒ”์œ„๊ฐ€ ๋„ˆ๋ฌด ๋„“์–ด์„œ ์ ์ ˆํ•œ ํƒ€์ž…์ด๋ผ๊ณ  ๋ณด๊ธฐ ์–ด๋ ค์šด ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค.
const releaseDates = pluck(albums, 'releaseDate'); // ํƒ€์ž…์ด (string | Date)[]
  • ๋ฒ”์œ„๋ฅผ ๋” ์ขํžˆ๊ธฐ ์œ„ํ•ด, keyof T์˜ ๋ถ€๋ถ„ ์ง‘ํ•ฉ์œผ๋กœ ๋‘ ๋ฒˆ์งธ ์ œ๋„ค๋ฆญ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋„์ž…ํ•ด์•ผ ํ•œ๋‹ค.
function pluck<T, K extends keyof T>(records: T[], key: K): T[K][] {
  return records.map(r => r[key]);
}
pluck(albums, 'releaseDate'); // ํƒ€์ž…์ด Date[]
pluck(albums, 'artist'); // ํƒ€์ž…์ด string[]
pluck(albums, 'recordingType'); // ํƒ€์ž…์ด RecordingType[]
pluck(albums, 'recordingDate'); // ~~~ '"recordingDate"' ํ˜•์‹์˜ ์ธ์ˆ˜๋Š” ... ํ˜•์‹์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์— ํ• ๋‹น๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.


# ๐Ÿ’ก ๊ฒฐ๋ก 

  • string์€ any์™€ ๋น„์Šทํ•œ ๋ฌธ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
    • ๋”ฐ๋ผ์„œ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ๋ฌดํšจํ•œ ๊ฐ’์„ ํ—ˆ์šฉํ•˜๊ณ  ํƒ€์ž… ๊ฐ„์˜ ๊ด€๊ณ„๋„ ๊ฐ์ถ”์–ด ๋ฒ„๋ฆฐ๋‹ค.
    • ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์€ ํƒ€์ž… ์ฒด์ปค๋ฅผ ๋ฐฉํ•ดํ•˜๊ณ  ์‹ค์ œ ๋ฒ„๊ทธ๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.
  • ๋ณ€์ˆ˜์˜ ๋ฒ”์œ„๋ฅผ ๋ณด๋‹ค ์ •ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด string ํƒ€์ž…๋ณด๋‹ค๋Š” ๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด ํƒ€์ž…์˜ ์œ ๋‹ˆ์˜จ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
    • ํƒ€์ž… ์ฒดํฌ๋ฅผ ๋” ์—„๊ฒฉํžˆ ํ•  ์ˆ˜ ์žˆ๊ณ  ์ƒ์‚ฐ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.
  • ๊ฐ์ฒด์˜ ์†์„ฑ ์ด๋ฆ„์„ ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์„ ๋•Œ๋Š” string ๋ณด๋‹ค๋Š” keyof T๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.