2024-06-04.md

๐Ÿก

DIL: ์ดํŽ™ํ‹ฐ๋ธŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ

์Šคํ„ฐ๋””: ์›”๊ฐ„ CS, https://github.com/monthly-cs/2024-05-effective-typescript
์ž‘์„ฑ์ผ: 2024-06-04
์ž‘์„ฑ์ž: dusunax


์•„์ดํ…œ 40: ํ•จ์ˆ˜ ์•ˆ์œผ๋กœ ํƒ€์ž… ๋‹จ์–ธ๋ฌธ ๊ฐ์ถ”๊ธฐ Hide Unsafe Type Assertions in Well-Typed Functions

  • ์™ธ๋ถ€ ํƒ€์ž… ์ •์˜๋Š” ๊ฐ„๋‹จํ•˜์ง€๋งŒ, ํ•จ์ˆ˜ ๋‚ด๋ถ€ ๋กœ์ง์ด ๋ณต์žกํ•œ ๊ฒฝ์šฐ!
    • ๋‚ด๋ถ€์— ํƒ€์ž… ๋‹จ์–ธ์„ ์‚ฌ์šฉํ•˜๊ณ , ์™ธ๋ถ€๋กœ ๋“œ๋Ÿฌ๋‚˜๋Š” ํƒ€์ž… ์ •์˜๋ฅผ ์ •ํ™•ํžˆ ๋ช…์‹œ
    • ํ”„๋กœ์ ํŠธ์— ํƒ€์ž… ๋‹จ์–ธ๋ฌธ์ด ๋“œ๋Ÿฌ๋‚˜ ์žˆ์ง€ ์•Š๋„๋ก ๊ฐ์ถ”๊ธฐ

์บ์‹œ ๋ž˜ํผ ํ•จ์ˆ˜

๋ฆฌ์•กํŠธ๋Š” useMemo๊ฐ€ ์žˆ๋‹ค.

  • ํ•จ์ˆ˜ ๋‚ด๋ถ€์— any๊ฐ€ ๋งŽ์ง€๋งŒ, cacheLast์˜ ํƒ€์ž… ์ •์˜์—๋Š” any๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์—, cacheLast๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ชฝ์—์„œ๋Š” any๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ๋Š”์ง€ ์•Œ์ง€ ๋ชปํ•œ๋‹ค.
    • any๋ฅผ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ, cacheLast๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ชฝ์—์„œ๋Š” ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์œ ์ง€ํ•จ
declare function cacheLast<T extends Function>(fn: T): T;
declare function shallowEqual(a: any, b: any): boolean;

// ๊ตฌํ˜„์ฒด
// ์›๋ณธ ํ•จ์ˆ˜๊ฐ€ ๊ฐ์ฒด์ฒ˜๋Ÿผ ์†์„ฑ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ํƒ€์ž…์ด ๋‹ฌ๋ผ์ง„๋‹ค
// (์—ฐ์†์ ์œผ๋กœ ํ˜ธ์ถœํ•  ๋•Œ this ๊ฐ’์ด ๋™์ผํ•œ ์ง€ ์ฒดํฌํ•˜์ง€ ์•Š์Œ)
function cacheLast<T extends Function>(fn: T): T {
  let lastArgs: any[] | null = null;
  let lastResult: any;

  return function (...args: any[]) {
    if (!lastArgs || !shallowEqual(lastArgs, args)) {
      lastResult = fn(...args); // ๋งˆ์ง€๋ง‰ ํ•จ์ˆ˜ ๋ฐ˜ํ™˜๊ฐ’
      lastArgs = args; // ๋งˆ์ง€๋ง‰ ๋งค๊ฐœ๋ณ€์ˆ˜
    }
    return lastResult;
  }; as unknown as T;
}

// this๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ฒฝ์šฐ
function cacheLastWithThis<T extends Function>(fn: T): T {
  let lastArgs: any[] | null = null;
  let lastResult: any;
  let lastContext: any; // lastContext ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋งˆ์ง€๋ง‰ ํ˜ธ์ถœ ์‹œ์˜ this ๊ฐ’์„ ์ €์žฅ

  return function (this: any, ...args: any[]) {
    if (!lastArgs || lastContext !== this || !shallowEqual(lastArgs, args)) {
      lastResult = fn.apply(this, args); // ๋งˆ์ง€๋ง‰ ํ•จ์ˆ˜ ๋ฐ˜ํ™˜๊ฐ’
      lastArgs = args; // ๋งˆ์ง€๋ง‰ ๋งค๊ฐœ๋ณ€์ˆ˜
      lastContext = this; // ๋งˆ์ง€๋ง‰ this ๊ฐ’
    }
    return lastResult;
  } as unknown as T;
}

shallowObjectEqual

  • ๊ฐ์ฒด๊ฐ€ ๊ฐ™์€ ์ง€ ์ฒดํฌํ•˜๊ธฐ ์œ„ํ•ด
  • ์•„๋ž˜์˜ (b as any)[key] ๋‹จ์–ธ์€ ์•ˆ์ „ํ•˜๋‹ค.
    • key in b(ํ‚ค๊ฐ€ ๊ฐ์ฒด b์— ์žˆ๋Š”์ง€)๋ฅผ ๋จผ์ € ํ™•์ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ
function shallowObjectEqual<T extends object>(a: T, b: T): boolean {
  // a, b: ๋น„๊ตํ•  ๊ฐ์ฒด
  for (const [key, value] of Object.entries(a)) {
    // ๊ฐ์ฒด `a`์˜ ๋ชจ๋“  ํ‚ค ๊ฐ’ ์Œ์„ ์ˆœํšŒ
    if (!(key in b) || value !== (b as any)[key]) {
      // ๊ฐ์ฒด b์—, ํ˜„์žฌ ํ‚ค๊ฐ€ ์กด์žฌํ•˜๋Š” ์ง€ ํ™•์ธ.
      // ํ‚ค๊ฐ€ ์—†๊ฑฐ๋‚˜, ๊ฐ์ฒด a์˜ ๊ฐ’ value๊ณผ ๋‹ค๋ฅด๋‹ค๋ฉด? false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
      return false;
    }
  }
  return Object.keys(a).length === Object.keys(b).length;
  // ๋‘ ๊ฐ์ฒด์˜ ํ‚ค ๊ฐฏ์ˆ˜๊ฐ€ ๊ฐ™์€ ์ง€ ์ฒดํฌ
  // ๋ชจ๋“  ํ‚ค์™€ ๊ฐ’์ด ์ผ์น˜ํ•˜๊ณ , ํ‚ค ๊ฐœ์ˆ˜๊ฐ€ ๋™์ผํ•˜๋‹ค -> true (์–•์€ ๋น„๊ต๊ฐ€ ์ฐธ)
}

Things to Remember

  • Sometimes unsafe type assertions and any types are necessary or expedient. When you need to use one, hide it inside a function with a correct signature.
    • ๋•Œ๋•Œ๋กœ ๋ถˆ๊ฐ€ํ”ผํ•˜๊ฑฐ๋‚˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด any๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค. ์ด ๋•Œ any๋ฅผ ์˜ฌ๋ฐ”๋ฅธ ์‹œ๊ทธ๋‹ˆ์ฒ˜์˜ ํ•จ์ˆ˜ ์•ˆ์— ์ˆจ๊ธฐ์ž. ํ•จ์ˆ˜ ๋ฐ”๊นฅ์˜ ํƒ€์ž… ์•ˆ์ •์„ฑ ์œ ์ง€
  • Don't compromise a function's type signature to fix type errors in the implementation.
    • ํƒ€์ž… ์—๋Ÿฌ๋ฅผ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•ด, ํ•จ์ˆ˜์˜ ํƒ€์ž… ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๋ฐ”๊พธ์ง€ ๋ง์ž. (ํ•จ์ˆ˜์˜ ํƒ€์ž… ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ)
  • Make sure you explain why your type assertions are valid, and unit test your code thoroughly.
    • ํƒ€์ž… ๋‹จ์–ธ์— ๋Œ€ํ•œ ์„ค๋ช…๊ณผ ์œ ๋‹› ํ…Œ์ŠคํŠธ ํ•„์š”