2024-05-18.md

๐Ÿก

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

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


์•„์ดํ…œ 10: ๊ฐ์ฒด ๋ž˜ํผ ํƒ€์ž… ํ”ผํ•˜๊ธฐ Avoid Object Wrapper Types (String, Number, Boolean, Symbol, BigInt)

  • ๊ธฐ๋ณธํ˜• ๊ฐ’์˜ 7ํƒ€์ž…: string, number, boolean, null, undefined, symbol(ES2015), bigint(์ตœ์ข… ํ™•์ • ๋‹จ๊ณ„)
    • immutable์ด๋ฉฐ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€์ง€ ์•Š๋Š”๋‹ค.
  • ๋ž˜ํผ ๊ฐ์ฒด: String, Number, Boolean, Symbol, BigInt
"primitive".charAt(3); // 'm'
  • JavaScript promptly coerces between primitives and objects.

string์˜ property์— ์ ‘๊ทผํ•˜๋ ค ํ•  ๋•Œ

The Wrapper Object: https://javascriptrefined.io/the-wrapper-object-400311b29151

  • stirng์„ String ๊ฐ์ฒด๋กœ wrappingํ•˜๊ณ  (coerce)
  • ๋ž˜ํผ ๊ฐ์ฒด์—์„œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  (new String(string), String์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์†ํ•˜๊ณ , ์†์„ฑ ์ฐธ์กฐ)
  • ๋ž˜ํ•‘ํ•œ ๊ฐ์ฒด๋ฅผ ๋ฒ„๋ฆฐ๋‹ค. (property๊ฐ€ resolved๋˜์—ˆ์„ ๋•Œ)

ํŠน์ง•

// ๋ชฝํ‚คํŒจ์น˜? ๋Ÿฐํƒ€์ž„์— ํ”„๋กœ๊ทธ๋žจ์˜ ์–ด๋–ค ๊ธฐ๋Šฅ์„ ์ˆ˜์ •ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ. js์—์„œ๋Š” ์ฃผ๋กœ ํ”„๋กœํ† ํƒ€์ž…
// Don't do this!
const originalCharAt = String.prototype.charAt;
String.prototype.charAt = function (pos) {
  console.log(this, typeof this, pos); // "primitive",  "string",  3
  return originalCharAt.call(this, pos);
};
console.log("primitive".charAt(3));
  • ๊ธฐ๋ณธํ˜•์— ์†์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด? ๋ž˜ํผ ๊ฐ์ฒด์™€ ํ•จ๊ป˜ ๋ฒ„๋ ค์ง
  • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํƒ€์ž… ์„ ์–ธ์€ ๊ธฐ๋ณธํ˜• ํƒ€์ž…์ด๋‹ค.
  • string์€ String์— ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋‹ค (String์€ string์— ํ• ๋‹นํ•  ์ˆ˜ ์—†๋‹ค)
const s: String = "primitive"; // ํ• ๋‹น ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๊ธฐ๋ณธํ˜• ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์ž
const n: Number = 12;
const b: Boolean = true;

// new ํ‚ค์›Œ๋“œ ์—†์ด BigInt์™€ Symbol์„ ํ˜ธ์ถœํ•˜๋ฉด ๊ธฐ๋ณธํ˜•์„ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•ด๋„ ๋จ
typeof BigInt(1234); // "bigint"
typeof Symbol("sym"); // "symbol"

Things to Remember

  • Avoid TypeScript object wrapper types. Use the primitive types instead: string instead of String, number instead of Number, boolean instead of Boolean, symbol instead of Symbol, and bigint instead of BigInt.
    • ๋ž˜ํผ ํƒ€์ž…์ด ์•„๋‹Œ ๊ธฐ๋ณธํ˜•(์›์‹œํ˜•) ํƒ€์ž… ์‚ฌ์šฉํ•˜๊ธฐ
  • Understand how object wrapper types are used to provide methods on primitive values. Avoid instantiating them or using them directly, with the exception of Symbol and BigInt.
    • ๋ž˜ํผ ๊ฐ์ฒด ํƒ€์ž…์€ ์›์‹œํ˜•์— ๋Œ€ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋จ. Symbol์ด๋‚˜ BigInt๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ

์•„์ดํ…œ 11: ์ž‰์—ฌ ์†์„ฑ ์ฒดํฌ excess property checking์˜ ํ•œ๊ณ„ ์ธ์ง€ํ•˜๊ธฐ Distinguish Excess Property Checking from Type Checking

interface Room {
  numDoors: number;
  ceilingHeightFt: number;
}

// A. ์ž‰์—ฌ ์†์„ฑ ์ฒดํฌ
const rA: Room = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: "present",
  // ~~~~~~~ Object literal may only specify known properties,
  //         and 'elephant' does not exist in type 'Room'
};

// B. ๊ตฌ์กฐ์  ํƒ€์ดํ•‘
// obj๋Š” Room ํƒ€์ž…์˜ ๋ถ€๋ถ„์ง‘ํ•ฉ์„ ํฌํ•จํ•˜๋ฏ€๋กœ Room์— ํ• ๋‹น ๊ฐ€๋Šฅํ•˜๋ฉฐ ํƒ€์ž… ์ฒด์ปค๋„ ํ†ต๊ณผ
const obj = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: "present",
};
const rB: Room = obj; // ํ†ต๊ณผ๋จ
// ์ž‰์—ฌ ์†์„ฑ ์ฒดํฌ๋Š” ํ• ๋‹น ๊ฐ€๋Šฅ ๊ฒ€์‚ฌ structural assignability check ์™€๋Š” ๋ณ„๋„์˜ ๊ณผ์ •

๋„“์€ ๋ฒ”์œ„์˜ ํƒ€์ž…, excess property check

interface Options {
  title: string;
  darkMode?: boolean;
}
function createWindow(options: Options) {
  if (options.darkMode) {
    setDarkMode();
  }
  // ...
}
createWindow({
  title: "Spider Solitaire",
  darkmode: true,
  // ~~~~~~~ Object literal may only specify known properties,
  //         but 'darkmode' does not exist in type 'Options'.
  //         Did you mean to write 'darkMode'?
});

const o1: Options = document; // document๋Š” ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด์ด ์•„๋‹ˆ๋ฏ€๋กœ - ์ฒดํฌ X
const o2: Options = new HTMLAnchorElement(); // ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด์ด ์•„๋‹ˆ๋ฏ€๋กœ - ์ž‰์—ฌ์†์„ฑ ์ฒดํฌ X
const o3: Options = { darkmode: true, title: "Ski Free" }; // ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด์ด๋ฏ€๋กœ - ์ฒดํฌ O
// ~~~~~~~~ 'darkmode' does not exist in type 'Options'...

const intermediate = { darkmode: true, title: "Ski Free" }; // right hand๋Š” ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด์ด๋‹ค
const o3: Options = intermediate; // right hand๋Š” ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด์ด ์•„๋‹ˆ๋‹ค. - ์ฒดํฌ X

const o = { darkmode: true, title: "MS Hearts" } as Options; // ํƒ€์ž… ๋‹จ์–ธ์ด๋ฏ€๋กœ ์ฒดํฌ X
  • ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์‚ฌ์šฉํ•ด ์ถ”๊ฐ€์ ์ธ ์†์„ฑ ์˜ˆ์ƒ
interface Options {
  darkMode?: boolean;
  [otherOptions: string]: unknown;
}
const o: Options = { darkmode: true }; // OK
  • weak type: ์„ ํƒ์ ์ธ ์†์„ฑ๋งŒ ๊ฐ€์ง
    • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ๊ฐ’ ํƒ€์ž…๊ณผ ์„ ์–ธ ํƒ€์ž…์˜ ๊ณตํ†ต๋œ ์†์„ฑ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ฒดํฌ ์ˆ˜ํ–‰
      • ์˜คํƒ€ ์žก๊ธฐ, ๊ตฌ์กฐ์ ์œผ๋กœ ์—„๊ฒฉํ•˜์ง€ ์•Š์Œ
      • ์„ ํƒ์  ํ•„๋“œ๋ฅผ ํฌํ•จํ•˜๋Š” option ๊ฐ™์€ ํƒ€์ž…์— ์œ ์šฉ
interface LineChartOptions {
  logscale?: boolean;
  invertedYAxis?: boolean;
  areaChart?: boolean;
}
function setOptions(options: LineChartOptions) {
  /* ... */
}

const opts = { logScale: true };
setOptions(opts);
//         ~~~~ Type '{ logScale: boolean; }' has no properties in common
//              with type 'LineChartOptions'

Things to Remember

  • When you assign an object literal to a variable with a known type or pass it as an argument to a function, it undergoes excess property checking.

    • "๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด"์„ ๋ณ€์ˆ˜์— ํ• ๋‹นํ•˜๊ฑฐ๋‚˜ ํ•จ์ˆ˜์— ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•  ๋•Œ, ์ž‰์—ฌ ์†์„ฑ ์ฒดํฌ excess property check๊ฐ€ ์ˆ˜ํ–‰๋œ๋‹ค
  • Excess property checking is an effective way to find errors, but it is distinct from the usual structural assignability checks done by the TypeScript type checker. Conflating these processes will make it harder for you to build a mental model of assignability. TypeScript types are not "closed" (pass:[Item 4]).

    • ์ž‰์—ฌ ์†์„ฑ ์ฒดํฌ๋Š” ์˜ค๋ฅ˜๋ฅผ ์ฐพ๋Š” ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•(๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด, ํƒ€์ž… ์—๋Ÿฌ)์ด์ง€๋งŒ, ๊ตฌ์กฐ์  ํ• ๋‹น ๊ฐ€๋Šฅ์„ฑ ์ฒดํฌ์™€๋Š” ์—ญํ• ์ด ๋‹ค๋ฅด๋‹ค(๊ตฌ์กฐ์  )

      | ๊ฒ€์‚ฌ | ์˜์–ด | ํ‚ค์›Œ๋“œ | ์„ค๋ช… | | ----------------------- | ------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------- | | ์ดˆ๊ณผ ์†์„ฑ ๊ฒ€์‚ฌ | Excess Property Checking | ์˜คํƒ€๋‚˜ ์˜๋„์น˜ ์•Š์€ ์†์„ฑ์„ ๊ฐ์ง€ | ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด์„ ๋‹ค๋ฅธ ํƒ€์ž…์— ํ• ๋‹นํ•  ๋•Œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์†์„ฑ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์—ฌ ์˜ค๋ฅ˜๋ฅผ ์ฐพ์•„๋‚ด๋Š” ๊ธฐ๋Šฅ | | ๊ตฌ์กฐ์  ํ• ๋‹น ๊ฐ€๋Šฅ์„ฑ ๊ฒ€์‚ฌ | Structural Assignability | ๊ตฌ์กฐ์  ํƒ€์ดํ•‘ | ํƒ€์ž… ๊ฐ„์˜ ํ˜ธํ™˜์„ฑ์„ ๊ฒฐ์ •ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ๊ฒ€์‚ฌ |

    • Excess Property Checking Example:

      interface Person {
        name: string;
        age: number;
      }
      
      const person: Person = {
        name: "Alice",
        age: 25,
        extraProp: "unexpected", // Error: Object literal may only specify known properties, and 'extraProp' does not exist in type 'Person'.
      };
      
    • Structural Assignability Example:

      interface Point {
        x: number;
        y: number;
      }
      
      const point = { x: 10, y: 20, z: 30 };
      
      const assignablePoint: Point = point;
      // ์˜ค๋ฅ˜ ์—†์Œ! 'point' ๊ฐ์ฒด์—๋Š” 'Point' ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์š”๊ตฌํ•˜๋Š” ์†์„ฑ์ด ๋ชจ๋‘ ํฌํ•จ๋˜์–ด ์žˆ์Œ.
      
  • Be aware of the limits of excess property checking: introducing an intermediate variable will remove these checks.

    • ์ถ”๊ฐ€ ์†์„ฑ ์ฒดํฌ์˜ ํ•œ๊ณ„ (์ค‘๊ฐ„ ๋‹จ๊ณ„์˜ ์ž„์‹œ ๋ณ€์ˆ˜)
  • A "weak type" is an object type with only optional properties. For these types, assignability checks require at least one matching property.

    • weak type์€ ์˜ต์…”๋„ ์†์„ฑ์œผ๋กœ๋งŒ ์ด๋ฃจ์–ด์ง„ ํƒ€์ž…
    • ์ ์–ด๋„ ํ•˜๋‚˜์˜ ์†์„ฑ์ด ๋งž์•„์•ผ ํ• ๋‹น

์•„์ดํ…œ 12: ํ•จ์ˆ˜ ํ‘œํ˜„์‹์— ํƒ€์ž… ์ ์šฉํ•˜๊ธฐ Apply Types to Entire Function Expressions When Possible

statement vs expression

function rollDice1(sides: number): number {} // ๋ฌธ์žฅ Statement
const rollDice2 = function (sides: number): number {}; // ํ‘œํ˜„์‹ Expression
const rollDice3 = (sides: number): number => {}; // ํ‘œํ˜„์‹ expression

ํ•จ์ˆ˜ ์ „์ฒด์˜ ํƒ€์ž…์„ ์ •์˜ํ•˜๊ธฐ

type DiceRollFn = (sides: number) => number;
// ํ•จ์ˆ˜ ํƒ€์ž…์˜ ์„ ์–ธ์˜ ๋ชฉ์ : ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ์˜ ๋ฐ˜๋ณต์„ ์ค„์ธ๋‹ค (๋ฐ˜๋ณต๋˜๋Š” ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ํ†ตํ•ฉ)
// ํ•จ์ˆ˜ ๊ตฌํ˜„๋ถ€๊ฐ€ ๋ถ„๋ฆฌ๋˜์–ด ๋กœ์ง์ด ๋ถ„๋ช…ํ•ด์ง„๋‹ค.
const rollDice: DiceRollFn = (sides) => {};
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ณตํ†ต ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜: ๊ณตํ†ต ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ ์œ„ํ•œ ํƒ€์ž… ์„ ์–ธ

    • JS์˜ MouseEvent, React์˜ MouseEventHandler

    • MouseEventHandler๋Š” ํ•จ์ˆ˜ ์ „์ฒด์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์ด๋‹ค.

      // @types/react/index.d.ts
      // MouseEventHandler
      // Element T๋ฅผ ์ œ๋„ค๋ฆญ์œผ๋กœ ๋ฐ›์•„์„œ MouseEvent<T>๋กœ EventHandler ํƒ€์ž…์— ๋„˜๊น€
      type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
      
      // (์ฐธ๊ณ ์šฉ)EventHandler
      type EventHandler<E extends SyntheticEvent<any>> = {
        bivarianceHack(event: E): void;
      }["bivarianceHack"];
      
  • ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ์ผ์น˜ํ•˜๋Š” ๋‹ค๋ฅธ ํ•จ์ˆ˜

    • ์˜ˆ์‹œ: fetch ํƒ€์ž… ํ™œ์šฉํ•˜๊ธฐ
// typescript/lib/lib.dom.d.ts์— ์žˆ๋Š” fetch ํƒ€์ž…
declare function fetch(
  input: RequestInfo,
  init?: RequestInit
): Promise<Response>;

// ๋‹ค๋ฅธ ํŒŒ์ผ์—์„œ ํ™œ์šฉ
// ํ•จ์ˆ˜ ์ „์ฒด์— ํƒ€์ž…(typeof fetch)๋ฅผ ์ ์šฉํ•ด, ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ํƒ€์ž…์„ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค
const checkedFetch: typeof fetch = async (input, init) => {
  const response = await fetch(input, init);
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }
  return response;
};

Things to Remember

  • Consider applying type annotations to entire function expressions, rather than to their parameters and return type.
    • ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๋ฆฌํ„ด ํƒ€์ž…์„ ๊ฐœ๋ณ„๋กœ ์„ ์–ธํ•˜์ง€ ์•Š๊ณ , ์ „์ฒด ํ•จ์ˆ˜ ํ‘œํ˜„์‹์˜ ํƒ€์ž…์„ ์„ ์–ธํ•˜๋„๋ก ํ•œ๋‹ค
  • If you're writing the same type signature repeatedly, factor out a function type or look for an existing one.
    • ๋ฐ˜๋ณต์ ์ธ ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์ž‘์„ฑํ•˜์ง€ ๋ง๊ณ , ํ•จ์ˆ˜ ํƒ€์ž…์„ ๋ถ„๋ฆฌํ•ด๋‚ด๊ฑฐ๋‚˜ & ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํƒ€์ž…์„ ์ฐพ๋„๋ก ํ•œ๋‹ค
  • If you're a library author, provide types for common callbacks.
    • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋งŒ๋“ค ๋•Œ, ๊ณตํ†ต ์ฝœ๋ฐฑ ํƒ€์ž…์„ ์ œ๊ณตํ•˜์ž
  • Use typeof fn to match the signature of another function, or Parameters and a rest parameter if you need to change the return type.
    • ๋‹ค๋ฅธ ํ•จ์ˆ˜์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด typeof fn์„ ์‚ฌ์šฉํ•œ๋‹ค

์•„์ดํ…œ 13 ํƒ€์ž…๊ณผ ์ธํ„ฐํŽ˜์ด์Šค์˜ ์ฐจ์ด์  ์•Œ๊ธฐ Know the Differences Between type and interface

  • named type์„ ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•
    • type๊ณผ interface๋กœ ์ •์˜
    • class๋Š” ๊ฐ’+ํƒ€์ž…

์ ‘๋‘์‚ฌ I, T๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ์ด์œ 

  • (๊ถŒ์žฅ์‚ฌํ•ญ) ์ ‘๋‘์‚ฌ๋กœ I, T๋ฅผ ๋ถ™์ด๋Š” ๊ฒƒ์€ C# ๊ด€๋ก€์ธ๋ฐ, ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ดˆ๊ธฐ์— ์‚ฌ์šฉํ–ˆ์—ˆ์ง€๋งŒ ์ง€๊ธˆ์€ ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ๊ถŒ์žฅํ•˜์ง€ ์•Š๋Š” ์Šคํƒ€์ผ์ด๋‹ค.
  • (ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ปดํŒŒ์ผ๋Ÿฌ) ํƒ€์ž…์ด๋‚˜ ์ธํ„ฐํŽ˜์ด์Šค ์ด๋ฆ„์— ๊ตณ์ด I๋‚˜ T๋ฅผ ๋ถ™์ด์ง€ ์•Š์•„๋„ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ด๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • (๊ฐ€๋…์„ฑ) IUser๋ณด๋‹ค User๊ฐ€ ๋” ์ง๊ด€์ ์ด๋ฉฐ, TProduct๋ณด๋‹ค Product๊ฐ€ ๋” ๋ช…ํ™•

type๊ณผ interface

  • ๊ณตํ†ต: ์ถ”๊ฐ€ ์†์„ฑ ๊ฒ€์‚ฌ, ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜, ํ•จ์ˆ˜ ํƒ€์ž… ์ •์˜, ์ œ๋„ค๋ฆญ, ํด๋ž˜์Šค implements
  • ํ™•์žฅ
    • interface: ํƒ€์ž…์„ ํ™•์žฅ
      • ์œ ๋‹ˆ์˜จ ํƒ€์ž…๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ํƒ€์ž… ํ™•์žฅ x (์œ ๋‹ˆ์˜จ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์—†๋‹ค)
    • type: ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™•์žฅ
// ํ™•์žฅ extends
interface IStateWithPop extends TState {
  population: number;
}
type TStateWithPop = IState & { population: number };

// ํด๋ž˜์Šค implements
class StateT implements TState {
  name: string = "";
  capital: string = "";
}
class StateI implements IState {
  name: string = "";
  capital: string = "";
}

type ํ™œ์šฉ

  • ํƒ€์ž… union

    type AorB = "a" | "b";
    
  • ๋ณ„๋„์˜ ํƒ€์ž…์„ ํ•˜๋‚˜์˜ ๋ณ€์ˆ˜๋ช…์œผ๋กœ ๋งคํ•‘ํ•  ๋•Œ

    type Input = {
      /* ... */
    };
    type Output = {
      /* ... */
    };
    interface VariableMap {
      [name: string]: Input | Output; // union
    }
    
  • union ํƒ€์ž…์— ์†์„ฑ์„ ์ถ”๊ฐ€ํ•œ ํƒ€์ž…

    type NamedVariable = (Input | Output) & { name: string };
    
  • ํŠœํ”Œ, ๋ฐฐ์—ด

    • interface๋กœ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋˜๋ฉด, concat๊ณผ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Œ
    • ์ง๊ด€์ ์ด์ง€ ์•Š์Œ
    • ์ˆซ์ž ์ธ๋ฑ์Šค ๋ฌธ์ œ (์•„์ดํ…œ 16)
    type Pair = [a: number, b: number];
    type StringList = string[];
    type NamedNums = [string, ...number[]];
    
    const ํŽ˜์–ด: Pair = [0, 1];
    const ๋„ค์ž„๋“œ๋„˜์ฆˆ: NamedNums = ["๐Ÿ", 0, 1];
    
    // Interface๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ
    // ํƒ€์ž…์˜ ํ˜•ํƒœ์™€, ํƒ€์ž… ์—๋Ÿฌ์˜ ๋ฉ”์‹œ์ง€(ํ•˜๋‹จ)๊ฐ€ ์ง๊ด€์ ์ด์ง€ ์•Š์Œ
    interface Tuples {
      0: number;
      1: number;
      length: 2;
    }
    
    • type ํŠœํ”Œ

      • length๊ฐ€ 1

        Type '[number]' is not assignable to type 'Pair'.
          Source has 1 element(s) but target requires 2
        
      • length๊ฐ€ 3

        Type '[number, number, number]' is not assignable to type 'Pair'.
        Source has 3 element(s) but target allows only 2.(2322)
        
    • interface๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ

      • length๊ฐ€ 1
        Property '1' is missing in type '[number]' but required in type 'Tuples'.
        
        image
      • length๊ฐ€ 3
        Type '[number, number, number]' is not assignable to type 'Tuples'.
          Types of property 'length' are incompatible.
            Type '3' is not assignable to type '2'.(2322)
        
        image

๋ณด๊ฐ• augment

  • interface๋งŒ ๊ฐ€๋Šฅ
interface IState {
  name: string;
  capital: string;
}
interface IState {
  // ๋ณด๊ฐ•๋จ!
  population: number;
}
const wyoming: IState = {
  name: "Wyoming",
  capital: "Cheyenne",
  population: 578_000,
}; // OK
  • ๋ณด๊ฐ•์„ ๋ณ€์ˆ˜ ์„ ์–ธ&ํ• ๋‹น์˜ ํ•˜๋‹จ์—์„œ ์ง„ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ํƒ€์ž… ์—๋Ÿฌ ์—†์Œ (์ „์—ญ ๋ฒ”์œ„)
  • ์ถฉ๋Œ ์—†์Œ: ๊ฐ ๋ณด๊ฐ•์€ ์ด์ „ ์„ ์–ธ๊ณผ ์ถฉ๋Œํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค(์˜ˆ: ๋™์ผํ•œ ์ด๋ฆ„์˜ ์†์„ฑ์ด ๋‹ค๋ฅธ ํƒ€์ž…์„ ๊ฐ€์ง€๋ฉด ์•ˆ ๋จ) image
  • ์˜ˆ์ œ์˜ ์„ค๋ช…์ฒ˜๋Ÿผ ํƒ€์ž…์„ ์–ธ ํŒŒ์ผ(d.ts) ๋“ฑ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ฑ„์šฐ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ ํ•ฉํ•  ๊ฒƒ์ด๋ผ ์ƒ๊ฐ

์„ ์–ธ ๋ณ‘ํ•ฉ declaration merging,

  • TS๋Š” ์—ฌ๋Ÿฌ ๋ฒ„์ „์˜ JS ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์—ฌ๋Ÿฌ ํƒ€์ž…์„ ๋ชจ์•„ ๋ณ‘ํ•ฉํ•œ๋‹ค
  • ์˜ˆ์‹œ
    • lib.es5.d.ts์˜ interface Array<T>
    • tsconfig.json lib์— ES2015๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด? ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” lib.es2015.d.ts์˜ interface Array<T>๋ฅผ ๋ณ‘ํ•ฉํ•œ๋‹ค (find์™€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ํฌํ•จ)

Type or Interface?

  • ๋ณต์žกํ•œ ํƒ€์ž…: ํƒ€์ž… ๋ณ„์นญ
  • ๊ฐ„๋‹จํ•œ ํƒ€์ž…: ์ผ๊ด€์„ฑ๊ณผ ๋ณด๊ฐ•์˜ ๊ด€์ ์—์„œ ๊ณ ๋ คํ•˜๊ธฐ
    • ์ฝ”๋“œ ๋ฒ ์ด์Šค์˜ ์ผ๊ด€์„ฑ์„ ๋”ฐ๋ฆ„
    • ํ–ฅํ›„์˜ ๋ณด๊ฐ•์˜ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š”๊ฐ€
      • API์— ๋Œ€ํ•œ ํƒ€์ž… ์„ ์–ธ: interface
      • ํ”„๋กœ์ ํŠธ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํƒ€์ž…: type

Things to Remember

  • Understand the differences and similarities between type and interface.
    • ํƒ€์ž…๊ณผ ์ธํ„ฐํŽ˜์ด์Šค์˜ ์ฐจ์ด์  & ๋น„์Šทํ•œ ์  ์•Œ๊ธฐ
  • Know how to write the same types using either syntax.
    • ๊ฐ ๋ฌธ๋ฒ•์œผ๋กœ ์ž‘์„ฑํ•˜๋Š” ๋ฒ• ์•Œ๊ธฐ
  • Be aware of declaration merging for interface and type inlining for type.
    • ์„ ์–ธ ๋ณ‘ํ•ฉ๊ณผ ๋ณด๊ฐ• ์•Œ๊ธฐ
  • For projects without an established style, prefer interface to type for object types.
    • ์ฝ”๋“œ ๋ฒ ์ด์Šค ์Šคํƒ€์ผ์ด ์—†์œผ๋ฉด => ํ”„๋กœ์ ํŠธ์— ์–ด๋–ค ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•  ์ง€ ๊ฒฐ์ •ํ•  ๋•Œ, ์ผ๊ด€๋œ ์Šคํƒ€์ผ ํ™•๋ฆฝ
    • ์ผ๊ด€์„ฑ๊ณผ ๋ณด๊ฐ•์˜ ๊ด€์ ์—์„œ ๊ณ ๋ ค

์•„์ดํ…œ 14: ํƒ€์ž… ์—ฐ์‚ฐ๊ณผ ์ œ๋„ˆ๋ฆญ ์‚ฌ์šฉ์œผ๋กœ ๋ฐ˜๋ณต ์ค„์ด๊ธฐ Use Type Operations and Generic Types to Avoid Repeating Yourself

์˜๋ฌธ ์„œ์ ์—์„œ๋Š” Item 15

DRY, don't repeat yourself

  • ํƒ€์ž…์˜ ์ค‘๋ณต ์ค„์ด๊ธฐ
    • ํƒ€์ž…์— ์ด๋ฆ„ ๋ถ™์ด๊ธฐ
    • ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๋ช…๋ช…๋œ ํƒ€์ž…์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ธฐ
    • ์ธํ„ฐํŽ˜์ด์Šค ํ™•์žฅ
    • ํƒ€์ž… ๊ฐ„ ๋งคํ•‘

ํƒ€์ž…์— ์ด๋ฆ„ ๋ถ™์ด๊ธฐ named type

interface Point2D {
  x: number;
  y: number;
}
function distance(a: Point2D, b: Point2D) {
  /* ... */
}

ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๋ช…๋ช…๋œ ํƒ€์ž…์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ธฐ function signature

type HTTPFunction = (url: string, opts: Options) => Promise<Response>;
const get: HTTPFunction = (url, opts) => {
  /* ... */
};
const post: HTTPFunction = (url, opts) => {
  /* ... */
};

์ธํ„ฐํŽ˜์ด์Šค ํ™•์žฅ extends interface

interface Person {
  firstName: string;
  lastName: string;
}

interface PersonWithBirthDate extends Person {
  birth: Date;
}

// ๋˜๋Š” type intersectiond๋ฅผ ์“ธ ์ˆ˜๋„ ์žˆ์Œ
type PersonWithBirthDate = Person & { birth: Date };

๋ถ€๋ถ„ ์ง‘ํ•ฉ subset

interface State {
  userId: string;
  pageTitle: string;
  recentFiles: string[];
  pageContents: string;
}

// State์„ ์ธ๋ฑ์‹ฑ
interface TopNavState {
  userId: State["userId"];
  pageTitle: State["pageTitle"];
  recentFiles: State["recentFiles"];
}

// ๋งคํ•‘๋œ ํƒ€์ž…
// ๋ฐฐ์—ด์˜ ํ•„๋“œ๋ฅผ ๋ฃจํ”„ ๋”
type TopNavState = {
  [K in "userId" | "pageTitle" | "recentFiles"]: State[K];
};

// Pick
// type Pick<T, K extends keyof T> = { [P in K]: T[P]; }
type TopNavState = Pick<State, "userId" | "pageTitle" | "recentFiles">;
  • Pick์€ ์ œ๋„ˆ๋ฆญ ํƒ€์ž…์ด๋‹ค
    • ํ•จ์ˆ˜์—์„œ ๋‘ ๊ฐœ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์•„์„œ ๊ฒฐ๊ณผ๊ฐ’๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋“ฏ, T์™€ K๋ฅผ ๋ฐ›์•„์„œ ๊ฒฐ๊ณผ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์˜ˆ์‹œ: ํƒœ๊ทธ๋ฅผ ๋ถ™์ด๊ธฐ ์œ„ํ•ด ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

interface SaveAction {
  type: "save";
  // ...
}
interface LoadAction {
  type: "load";
  // ...
}
type Action = SaveAction | LoadAction;
// type ActionType = "save" | "load"; // Repeated types!

type ActionType = Action["type"];
//   ^? type ActionType = "save" | "load"
// ์ค‘๋ณต ์ฝ”๋“œ ์—†์ด Action์„ ์ธ๋ฑ์‹ฑ

type ActionRecord = Pick<Action, "type">;
//   ^? type ActionRecord = { type: "save" | "load"; }
// Pick๊ณผ ์ธ๋ฑ์‹ฑ์€ ํ˜•ํƒœ๊ฐ€ ๋‹ค๋ฆ„

์˜ˆ์‹œ: ์ƒ์„ฑํ•˜๊ณ  ๋‚œ ๋‹ค์Œ ์—…๋ฐ์ดํŠธ ๋˜๋Š” ํด๋ž˜์Šค

  • ํƒ€์ž… ๋Œ€๋ถ€๋ถ„์ด ์„ ํƒ์  ํ•„๋“œ
    • [k in keyof Options]?: Options[k]
    • ๋งคํ•‘๋œ ํƒ€์ž…์„ ์ˆœํšŒํ•˜๋ฉฐ Option ๋‚ด k ๊ฐ’ ์†์„ฑ ์ฐพ๊ธฐ => ๊ฐ ์†์„ฑ์„ optionalํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ
interface Options {
  width: number;
  height: number;
  color: string;
  label: string;
}
class UIWidget {
  constructor(init: Options) {}
  update(options: OptionsUpdate) {}
}

// OptionsUpdate ์ •์˜ํ•˜๊ธฐ
// ๐Ÿšซ ์ค‘๋ณต ์ฝ”๋“œ
interface OptionsUpdate {
  width?: number;
  height?: number;
  color?: string;
  label?: string;
}

// in์œผ๋กœ ๋ฃจํ”„
type OptionsUpdate = { [k in keyof Options]?: Options[k] };

// keyof์€? ์†์„ฑ ํƒ€์ž…์˜ ์œ ๋‹ˆ์˜จ ๋ฐ˜ํ™˜
type OptionsKeys = keyof Options;
//   ^? type OptionsKeys = keyof Options
//      (equivalent to "width" | "height" | "color" | "label")

// Partial ํŒจํ„ด
class UIWidget {
  constructor(init: Options) {}
  update(options: Partial<Options>) {}
}

๊ฐ’์— ํ˜•ํƒœ์— ํ•ด๋‹นํ•˜๋Š” ํƒ€์ž… ์ •์˜

  • value to type
const INIT_OPTIONS = {
  width: 640,
  height: 480,
  color: "#00FF00",
  label: "VGA",
};

// typeof์€ js์˜ typeof์ด ์•„๋‹˜
// ts์˜ typeof: `๊ฐ’`์„ ์ฝ์–ด์„œ, `ํƒ€์ž…`์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
type Options = typeof INIT_OPTIONS;
  • return value to type
// ReturnType ์ œ๋„ˆ๋ฆญ
type UserInfo = ReturnType<typeof getUserInfo>;
  • DRY ์›์น™์„ ์ง€์น  ๋•Œ ์ œ๋„ค๋ฆญ์ด ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋จ~!
    • ๋งค๊ฐœ๋ณ€์ˆ˜ ์ œํ•œ
type Pick<T, K extends keyof T> = {
  // extends๋Š” ํ™•์žฅ์ด ์•„๋‹Œ ๋ถ€๋ถ„ ์ง‘ํ•ฉ
  [k in K]: T[k];
};

Things to Remember

  • The DRY (don't repeat yourself) principle applies to types as much as it applies to logic.
    • DRY ์›์น™์„ ํƒ€์ž…์—๋„ ์ตœ๋Œ€ํ•œ ์ ์šฉ
  • Name types rather than repeating them. Use extends to avoid repeating fields in interfaces.
    • ํƒ€์ž…์„ ๋ฐ˜๋ณตํ•˜์ง€ ๋ง๊ณ  name type์„ ์‚ฌ์šฉํ•˜์ž. field ์ค‘๋ณต์—๋Š” interface extends๋ฅผ ์‚ฌ์šฉํ•˜์ž
  • Build an understanding of the tools provided by TypeScript to map between types. These include keyof, typeof, indexing, and mapped types.
    • ๋งคํ•‘์„ ์œ„ํ•ด ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๋„๊ตฌ๋ฅผ ์•Œ์•„๋‘์ž: keyof, typeof, indexing, and mapped types
  • Generic types are the equivalent of functions for types. Use them to map between types instead of repeating type-level operations.
    • ํƒ€์ž… ๋ ˆ๋ฒจ์˜ ๋ฐ˜๋ณต ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์ž
    • ์ œ๋„ค๋ฆญ ํƒ€์ž…์€ ํƒ€์ž…์„ ์œ„ํ•œ ํ•จ์ˆ˜์™€ ๊ฐ™๋‹ค.
  • Familiarize yourself with generic types defined in the standard library, such as Pick, Partial, and ReturnType.
    • ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ •์˜๋œ ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์ž (๊ณ ์ƒํ•˜๊ธฐ ์‹ซ์œผ๋ฉด)
    • Pick, Partial, ReturnType
  • Avoid over-application of DRY: make sure the properties and types you're sharing are really the same thing.
    • ์ค‘๋ณต๋œ ํƒ€์ž…์˜ ๊ฒฝ์šฐ: ๊ธฐ๋ณธ์ ์œผ๋กœ ์„œ๋กœ ๋‹ค๋ฅธ ์†์„ฑ๊ณผ ์˜๋ฏธ๋ฅผ ๊ฐ€์งˆ ๋•Œ๋Š” DRY๋ฅผ ํ”ผํ•˜๊ธฐ
    • ๋™์ผํ•œ ์œ ํ˜•์œผ๋กœ ๊ฒฐํ•ฉํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.

Item 15: ๋™์  ๋ฐ์ดํ„ฐ์— ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์ ์šฉํ•˜๊ธฐ: Prefer More Precise Alternatives to Index Signatures

์˜๋ฌธ ์„œ์ ์—์„œ๋Š” Item 16

์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜ Index Signatures

// [ํ‚ค_์ด๋ฆ„: ํ‚ค_ํƒ€์ž…]: ๊ฐ’_ํƒ€์ž…
type Rocket = { [์†์„ฑ: string]: string }; // index signature
// - ํ‚ค ์ด๋ฆ„์œผ๋กœ ๋ฌด์—‡์ด๋“  ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ž๋™ ์™„์„ฑ x
// - ํ‚ค๋งˆ๋‹ค ๋‹ค๋ฅธ ํƒ€์ž…์„ ๊ฐ€์งˆ ์ˆ˜ ์—†๋‹ค.

const rocket: Rocket = {
  name: "Falcon 9",
  variant: "v1.0",
  thrust: "4,940 kN",
};
  • ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜๋Š” ๋™์  ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋™์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œํ˜„ํ•˜๊ณ , ๊ฐ์ฒด๋กœ ๋งคํ•‘ํ•˜๊ณ , ๋ฆฌํ„ด ๊ฐ’์„ ๋‹จ์–ธ๋ฌธ์„ ์‚ฌ์šฉ

์—ฐ๊ด€ ๋ฐฐ์—ด associative array

  • ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ ๋ฌธ์ œ ์šฐํšŒ (์•„์ดํ…œ 58)
//
interface Row1 {
  [column: string]: number;
}

// ์„ ํƒ์  ํ•„๋“œ
interface Row2 {
  a: number;
  b?: number;
  c?: number;
  d?: number;
} // Better

// ์œ ๋‹ˆ์˜จ ํƒ€์ž…
type Row3 =
  | { a: number }
  | { a: number; b: number }
  | { a: number; b: number; c: number }
  | { a: number; b: number; c: number; d: number }; // Also better: ๋ฒˆ๊ฑฐ๋กœ์›€

// Record
type Vec3D = Record<"x" | "y" | "z", number>;
//   ^? type Vec3D = {
//        x: number;
//        y: number;
//        z: number;
//      }

unknown props

interface ButtonProps {
  title: string;
  onClick: () => void;
  [otherProps: string]: unknown; // unknown props
}

renderAButton({
  title: "Roll the dice",
  onClick: () => alert(1 + Math.floor(20 * Math.random())),
  theme: "Solarized", // ์ถ”๊ฐ€ props
});

Things to Remember

  • Understand the drawbacks of index signatures: much like any, they erode type safety and reduce the value of language services.
    • index signature๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด any์ฒ˜๋Ÿผ ์•ˆ์ •์„ฑ์ด ๋–จ์–ด์ง€๊ณ , ์–ธ์–ด ์„œ๋น„์Šค๋ฅผ ์ œ๊ณต ๋ฐ›๊ธฐ ํž˜๋“ค๋‹ค.
    • undefined๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•œ๋‹ค
  • Prefer more precise types to index signatures when possible: interfaces, Map, Records, mapped types, or index signatures with a constrained key space.
    • ๋” ์ •ํ™•ํ•œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค: interfaces, Map, Records, mapped types

์•„์ดํ…œ 16: number ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ณด๋‹ค Array, ํŠœํ”Œ, ArrayLike์„ ์‚ฌ์šฉํ•˜๊ธฐ Avoid Numeric Index Signatures

์˜๋ฌธ ์„œ์ ์—์„œ๋Š” Item 17

  • ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ๊ฐ์ฒด๋ž€ ํ‚ค/๊ฐ’ ์Œ์˜ ๋ชจ์Œ์ด๋‹ค.
  • ํ‚ค๋Š” ๋ณดํ†ต ๋ฌธ์ž์—ด์ด๋ฉฐ, ๊ฐ’์€ ๋ฌด์—‡์ด๋“  ๋  ์ˆ˜ ์žˆ๋‹ค.

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๊ฐ์ฒด

  • ๋ณต์žกํ•œ ๊ฐ์ฒด๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉํ•˜๋ ค ํ•  ๋•Œ? image

  • ์ˆซ์ž๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉํ•˜๋ฉด? ๋Ÿฐํƒ€์ž„์ด ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.

    const x = { 1: 1 };
    console.log(x); // {"1": 1}
    
  • ๋ฐฐ์—ด์€? ๊ฐ์ฒด๋‹ค.

    • ์ˆซ์ž ์ธ๋ฑ์Šค๋Š”? ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜๋˜์–ด ์‚ฌ์šฉ๋œ๋‹ค.
    • x["1"] ๋ฌธ์ž์—ด ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฐฐ์—ด ์š”์†Œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Œ
    • Object.keys()๋กœ ๋ฐฐ์—ด์˜ ํ‚ค๋ฅผ ๋‚˜์—ดํ•˜๋ฉด? ํ‚ค๋Š” ๋ฌธ์ž์—ด์ด๋‹ค
    let array1 = [1, 2, 3];
    console.log(array1["0"]); // 1
    
    for (const key in array1) {
      console.log(key, array1[key]);
      // "0", 1
      // "1", 2
      // "2", 3
    }
    

for in์€ ๋Š๋ฆฌ๋‹ค

  • ํƒ€์ž…์ด ๋ถˆํ™•์‹คํ•˜๋‹ค๋ฉด for-in ๋ฃจํ”„๋Š” ๋Š๋ฆฌ๋‹ค
    • ๊ฐ์ฒด์˜ ๋ชจ๋“  ์—ด๊ฑฐ ๊ฐ€๋Šฅํ•œ ์†์„ฑ๊ณผ ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ์˜ ์†์„ฑ๊นŒ์ง€ ๊ฒ€์‚ฌํ•˜๋ฏ€๋กœ ๋Š๋ฆฌ๋‹ค
    • ์ด๋กœ ์ธํ•ด ์†์„ฑ ํ•„ํ„ฐ๋ง๊ณผ ์ˆœ์„œ ์œ ์ง€ ๋“ฑ ์ถ”๊ฐ€์ ์ธ ์ž‘์—…์ด ํ•„์š”ํ•ด ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฏธ์นœ๋‹ค
  • for-of๋‚˜ for(;;) ๋ฃจํ”„ ์‚ฌ์šฉํ•˜๊ธฐ

ArrayLike

  • number ํƒ€์ž…์˜ ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜

    • ์ˆซ์ž๋ฅผ ์‚ฌ์šฉํ•ด ์ธ๋ฑ์Šค ํ•ญ๋ชฉ์„ ์ง€์ •ํ•œ๋‹ค๋ฉด, ์ˆซ์ž ์†์„ฑ์— ๋Œ€ํ•œ ์˜คํ•ด๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋‹ค
  • Array์˜ ํƒ€์ž…์„ ๊ฐ€์ง€์ง€ ์•Š์€, ์–ด๋–ค ๊ธธ์ด๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” ๋ฐฐ์—ด๊ณผ ๋น„์Šทํ•œ ํ˜•ํƒœ์˜ ํŠœํ”Œ์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

    • ArrayLike์„ ์‚ฌ์šฉํ•œ๋‹ค
    // ArrayLike๋Š” ์ˆซ์ž ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜์™€ length๋ฅผ ๊ฐ€์ง
    const tupleLike: ArrayLike<string> = {
      "0": "A",
      "1": "B",
      length: 2,
    }; // OK
    

Things to Remember

  • Understand that arrays are objects, so their keys are strings, not numbers. number as an index signature is a purely TypeScript construct designed to help catch bugs.
    • ๋ฐฐ์—ด์€ ๊ฐ์ฒด์ด๋ฏ€๋กœ, ํ‚ค๋Š” ๋ฌธ์ž์—ด์ด๋‹ค.
    • ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜์˜ ๋ฐฐ์—ด[number]์—์„œ number๋Š” ๋ฒ„๊ทธ๋ฅผ ์žก๊ธฐ ์œ„ํ•œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ์ด๋‹ค.
  • Prefer Array, tuple, ArrayLike, or Iterable types to using number in an index signature yourself.
    • ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜์˜ number๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋ณด๋‹ค Array๋‚˜ ํŠœํ”Œ, ArrayLike์„ ์‚ฌ์šฉํ•˜์ž

์•„์ดํ…œ 17 ๋ณ€๊ฒฝ ๊ด€๋ จ๋œ ์˜ค๋ฅ˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด readonly ์‚ฌ์šฉํ•˜๊ธฐ: Use readonly to Avoid Errors Associated with Mutation

์˜๋ฌธ ์„œ์ ์—์„œ๋Š” Item 14

  • readonly ์ ‘๊ทผ ์ œ์–ด์ž
    • ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์„ ์–ธ

readonly number[]

  • readonly number[]๋Š” number[]์˜ ์„œ๋ธŒํƒ€์ž…์ด๋‹ค
  • ๋ฐฐ์—ด์˜ ์š”์†Œ๋ฅผ ์ฝ์„ ์ˆ˜๋Š” ์žˆ์ง€๋งŒ, ์“ธ ์ˆ˜๋Š” ์—†๋‹ค image
  • length๋ฅผ ์ฝ์„ ์ˆ˜๋Š” ์žˆ์ง€๋งŒ, ๋ฐ”๊ฟ€ ์ˆ˜ ์—†๋‹ค
  • ๋ฐฐ์—ด์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†๋‹ค
// triangular number ์‚ผ๊ฐ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ์ฝ”๋“œ
// 1, 1+2, 1+2+3, ...

function printTriangles(n: number) {
  const nums = [];
  for (let i = 0; i < n; i++) {
    nums.push(i);
    console.log(arraySum(nums));
  }
}
printTriangles(5);

function arraySum(arr: number[]) {
  let sum = 0;
  for (const num of arr) {
    sum += num;
  }
  return sum;
}

ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ readonly๋กœ ์„ ์–ธํ–ˆ์„ ๋•Œ

  • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ํ•จ์ˆ˜ ๋‚ด์—์„œ ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์ฒดํฌํ•œ๋‹ค
  • ํ˜ธ์ถœํ•˜๋Š” ์ชฝ์—์„œ ํ•จ์ˆ˜๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๋ณด์žฅ์„ ๋ฐ›๋Š”๋‹ค
  • ๋ช…์‹œ์ ์œผ๋กœ ์–ธ๊ธ‰ํ•˜์ง€ ์•Š๋Š”ํ•œ, ํ•จ์ˆ˜๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ง€๋งŒ, ์•”๋ฌต์ ์ธ ๋ฐฉ๋ฒ•์ด๋ฏ€๋กœ ํƒ€์ž…์ฒดํฌ์— ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋‹ค
  • ๋‹จ์ 
    • ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋‹ค๋ฅธ ํ•จ์ˆ˜๋„ readonly๋กœ ๋ณ€๊ฒฝ => ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ๋†’์ž„
    • ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ ๋‹จ์–ธ๋ฌธ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ๋‹จ์–ธํ•ด์•ผํ•จ
  • ์žฅ์ 
    • ์ง€์—ญ ๋ณ€์ˆ˜์™€ ๊ด€๋ จ๋œ ๋ณ€๊ฒฝ ์˜ค๋ฅ˜ ๋ฐฉ์ง€

shallow ๋ณ€๊ฒฝ ๋ฐฉ์ง€

  • ์–•์€(Shallow) ๋ณ€๊ฒฝ ๋ฐฉ์ง€์ด๋ฏ€๋กœ, ์ค‘์ฒฉ๋œ ๋ฐฐ์—ด์˜ ๋‚ด๋ถ€ ๊ฐ’์€ ์—ฌ์ „ํžˆ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Œ (๊ฐ์ฒด์— ์ ์šฉํ•˜๋Š” Readonly ์ œ๋„ค๋ฆญ์˜ ๊ฒฝ์šฐ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€)
  • DeepReadonly๋Š”? ts-essential ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ. ๊นŠ์€ readonly ํƒ€์ž…
  • ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜์—๋„ readonly ์“ธ ์ˆ˜ ์žˆ๋‹ค
// let obj: {readonly [k: string]: number | string} = {}
let obj: Readonly<{ [k: string]: number | string }> = {};
// obj.hi = "๐Ÿ™Œ";
obj = { ...obj, hi: "๐Ÿ‘" };

Things to Remember

  • If your function does not modify its parameters, declare them readonly (arrays) or Readonly (object types). This makes the function's contract clearer and prevents inadvertent mutations in its implementation.
    • ํ•จ์ˆ˜๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด? readonly array, Readonly object๋ฅผ ์ ์šฉํ•ด ์˜๋„์น˜ ์•Š์€ ๋ณ€๊ฒฝ์„ ๋ฐฉ์ง€
  • Understand that readonly and Readonly are shallow, and that Readonly only affects properties, not methods.
    • ์–•๊ฒŒ ๋™์ž‘ํ•œ๋‹ค & ๋ฉ”์†Œ๋“œ์—๋Š” ์ ์šฉ๋˜์ง€ ์•Š์Œ์„ ๋ช…์‹ฌ
  • Use readonly to prevent errors with mutation and to find the places in your code where mutations occur.
    • readonly๋ฅผ ์‚ฌ์šฉํ•ด์„œ, ๋ณ€๊ฒฝํ•˜๋ฉด์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜ ๋ฐฉ์ง€ํ•˜๊ณ  ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•˜๋Š” ์ฝ”๋“œ ์‰ฝ๊ฒŒ ์ฐพ๊ธฐ
  • Understand the difference between const and readonly: the former prevents reassignment, the latter prevents mutation.
    • const์™€ readonly ์ฐจ์ด์  => const๋Š” ์žฌํ• ๋‹นx. readonly๋Š” ๋ณ€๊ฒฝ์„ ๋ฐฉ์ง€ (์žฌํ• ๋‹น... ๋œ๋‹ค...)

์•„์ดํ…œ 18: ๋งคํ•‘๋œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ’์„ ๋™๊ธฐํ™”ํ•˜๊ธฐ Use Record Types to Keep Values in Sync

์˜๋ฌธ ์„œ์ ์—์„œ๋Š” Item 61

(fail closed) ๋ณด์ˆ˜์  ์ ‘๊ทผ๋ฒ• Conservative Approach์„ ํ†ตํ•ด ๊ฐ’ ๋ณ€๊ฒฝ

  • props์˜ ๋ชจ๋“  ์†์„ฑ์„ ๊ฒ€์‚ฌํ•˜์—ฌ ๋ณ€๊ฒฝ์ด ์žˆ๋Š”์ง€ ํ™•์ธ
  • onClick ์†์„ฑ์„ ์ œ์™ธํ•œ ๋ชจ๋“  ์†์„ฑ์˜ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๋Š” ์ ‘๊ทผ๋ฒ•
function shouldUpdate(oldProps: ScatterProps, newProps: ScatterProps) {
  for (const kStr in oldProps) {
    const k = kStr as keyof ScatterProps;
    if (oldProps[k] !== newProps[k]) {
      if (k !== "onClick") return true;
    }
  }
  return false;
}

(fail open) ์‹คํŒจ์— ์—ด๋ฆฐ ์ ‘๊ทผ๋ฒ•์„ ํ†ตํ•ด ๊ฐ’ ๋ณ€๊ฒฝ

  • ํŠน์ • ์†์„ฑ๋งŒ ๊ฒ€์‚ฌํ•˜์—ฌ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ๋ชจ๋“  ์†์„ฑ์„ ๊ฒ€์‚ฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜, ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ ("first, do no harm" ์›์น™์— ์œ„๋ฐฐ)
function shouldUpdate(oldProps: ScatterProps, newProps: ScatterProps) {
  return (
    oldProps.xs !== newProps.xs ||
    oldProps.ys !== newProps.ys ||
    oldProps.xRange !== newProps.xRange ||
    oldProps.yRange !== newProps.yRange ||
    oldProps.color !== newProps.color
    // (no check for onClick)
  );
}

์ƒˆ๋กœ์šด ์†์„ฑ์ด ์ถ”๊ฐ€๋  ๋•Œ ๊ฐ’ ๋ณ€๊ฒฝ

  • ๋ณด์ˆ˜์  ์ ‘๊ทผ๋ฒ•๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ, ํƒ€์ž… ์ฒด์ปค๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ด๋ฅผ ๊ฐ•์ œํ•˜๋„๋ก ํ•จ
    • ๋งคํ•‘๋œ ํƒ€์ž… REQUIRES_UPDATE ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋–ค ์†์„ฑ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ์ง€ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜
  • ์ƒˆ๋กœ์šด ์†์„ฑ์ด ์ถ”๊ฐ€๋  ๋•Œ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋ฏ€๋กœ ํ™•์žฅ์„ฑ์ด ๋†’์Œ
const REQUIRES_UPDATE: Record<keyof ScatterProps, boolean> = {
  xs: true,
  ys: true,
  xRange: true,
  yRange: true,
  color: true,
  onClick: false,
};

function shouldUpdate(oldProps: ScatterProps, newProps: ScatterProps) {
  for (const kStr in oldProps) {
    const k = kStr as keyof ScatterProps;
    if (oldProps[k] !== newProps[k] && REQUIRES_UPDATE[k]) {
      // ๋งคํ•‘๋œ ํƒ€์ž…์„ ํ†ตํ•ด ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ฝ”๋“œ์— ์ œ์•ฝ์„ ๊ฐ•์ œํ•˜๋„๋ก ํ•จ
      return true;
    }
  }
  return false;
}

Things to Remember

  • Recognize the fail open versus fail closed dilemma.
    • ๊ฐ ์ ‘๊ทผ๋ฒ•์— ๋Œ€ํ•œ ๋”œ๋ ˆ๋งˆ ์ดํ•ด
  • Use Record types to keep related values and types synchronized.
    • Record type, ๋งคํ•‘๋œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด ๊ฐ’๊ณผ ํƒ€์ž…์„ ๋™๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Consider using Record types to force choices when adding new properties to an interface.
    • ์ธํ„ฐํŽ˜์ด์Šค์— ์ƒˆ๋กœ์šด ์†์„ฑ์„ ์ถ”๊ฐ€ํ•  ๋•Œ, Record type์„ ์‚ฌ์šฉํ•ด ์„ ํƒ์„ ๊ฐ•์ œํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

english
inadvertent [inยทadยทvertยทent]: not resulting from or achieved through deliberate planning.
conservative [conยทservยทaยทtive] ๋ณด์ˆ˜์ 