2024-06-01.md

๐Ÿก

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

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


์•„์ดํ…œ 35: ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹Œ, API์™€ ๋ช…์„ธ๋ฅผ ๋ณด๊ณ  ํƒ€์ž… ๋งŒ๋“ค๊ธฐ Avoid Types Based on Anecdotal Data

ํ”„๋กœ์ ํŠธ ์™ธ๋ถ€์˜ ํƒ€์ž…

  • ํŒŒ์ผ ํ˜•์‹, API, ๋ช…์„ธ specification
  • ํƒ€์ž…์„ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  ์ž๋™ ์ƒ์„ฑ (๋ช…์„ธ๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ์ƒ์„ฑ)

์˜ˆ์‹œ: DefinitelyTyped์— ํฌํ•จ๋œ ํƒ€์ž… ์ •์˜ ํŒŒ์ผ ์‚ฌ์šฉํ•˜๊ธฐ

  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: ๋ช…์„ธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํƒ€์ž…์„ ์ž‘์„ฑํ•ด์„œ, ํ˜„์žฌ๊นŒ์ง€ ๊ฒฝํ—˜ํ•œ ๋ฐ์ดํ„ฐ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ชจ๋“  ๊ฐ’์— ๋Œ€ํ•ด ์ž‘๋™ํ•œ๋‹ค๋Š” ํ™•์‹ ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
  • API: API ๋ช…์„ธ๋กœ๋ถ€ํ„ฐ ํƒ€์ž…์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ทธ๋ ‡๊ฒŒ ํ•˜๊ธฐ.
  • GraphQL
    • ์ž์ฒด์ ์œผ๋กœ ํƒ€์ž…์ด ์ž˜ ์ •์˜๋œ API
    • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์™€ ๋น„์Šทํ•œ ํƒ€์ž… ์‹œ์Šคํ…œ ์‚ฌ์šฉ. ๋ชจ๋“  ์ฟผ๋ฆฌ์™€ ์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธํ•˜๋Š” ์Šคํ‚ค๋งˆ๋กœ ์ด๋ฃจ์–ด์ง
    • ํŠน์ • ์ฟผ๋ฆฌ์— ํƒ€์ž…์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. (null ์—ฌ๋ถ€ ๋ชจ๋ธ๋ง)
// ๋ช…์‹œ์  ์ฐจ๋‹จ + ์˜ค๋ฅ˜ ๋ฐœ์ƒ
const { geometry } = f;
if (geometry) {
  if (geometry.type === "GeometryCollection") {
    // ํƒœ๊ทธ๋œ ์œ ๋‹ˆ์˜จ
    // GeometryCollection๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ฐจ๋‹จ, ์—๋Ÿฌ throw!
    throw new Error("GeometryCollections are not supported.");
  }
  // ์ •์ œ๋œ ํƒ€์ž…์— ํ•œํ•ด์„œ ์ฐธ์กฐ๋ฅผ ํ—ˆ์šฉ
  helper(geometry.coordinates); // OK
}

// ์กฐ๊ฑด ๋ถ„๊ธฐ + ํ—ฌํผ ํ•จ์ˆ˜ ๋ถ„๋ฆฌ
const geometryHelper = (g: Geometry) => {
  if (g.type === "GeometryCollection") {
    g.geometries.forEach(geometryHelper);
  } else {
    helper(g.coordinates); // OK
  }
};

const { geometry } = f;
if (geometry) {
  geometryHelper(geometry);
}

GraphQL

query getLicense($owner:String!, $name:String!){ // GraphQL์˜ String ํƒ€์ž…. nullable์ด๋ผ null ์•„๋‹˜ ๋‹จ์–ธ
  repository(owner: $owner, name: $name){
    description
    licenseInfo {
      spdxId
      name
    }
  }
}

Apollo

  • graphQL ์ฟผ๋ฆฌ๋ฅผ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ๋„๊ตฌ
$ apollo client:codegen \ // Apollo CLI์˜ ์ฝ”๋“œ ์ƒ์„ฑ ๋ช…๋ น์–ด
  --endpoint https://api.github.com/graphql \ // endpoint ์š”๊ธฐ์„œ ์Šคํ‚ค๋งˆ๋ฅผ ์–ป์Œ
  --include license.graphql \ // ํƒ€์ž… ์ƒ์„ฑ์— ํฌํ•จํ•  GraphQL ํŒŒ์ผ
  --target typescript // TypeScript ํƒ€์ž… ์ •์˜๋กœ ๋ณ€ํ™˜
  • codegen
    • ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์‘๋‹ต ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
    • ์ฃผ์„์„ JSDoc์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ํŽธ์ง‘๊ธฐ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ

๋ช…์„ธ ์ •๋ณด๋‚˜ ๊ณต์‹ ์Šคํ‚ค๋งˆ๊ฐ€ ์—†๋‹ค๋ฉด?

  • ๋ฐ์ดํ„ฐ๋กœ๋ถ€ํ„ฐ ํƒ€์ž…์„ ์ƒ์„ฑํ•ด์•ผํ•จ
    • quicktype ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ƒ์„ฑ๋œ ํƒ€์ž…์ด ์‹ค์ œ ๋ฐ์ดํ„ฐ์™€ ์ผ์น˜ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.
  • ์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ์ž๋™ ํƒ€์ž… ์ƒ์„ฑ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.
    • ex) ๋ธŒ๋ผ์šฐ์ € DOM API ํƒ€์ž…
    • ๋ณต์žกํ•œ ์‹œ์Šคํ…œ์„ ์ •ํ™•ํžˆ ๋ชจ๋ธ๋งํ•˜๊ณ , ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์˜ค๋ฅ˜๋‚˜ ์‹ค์ˆ˜๋ฅผ ์žก์„ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ

์•„์ดํ…œ 36: ํ•ด๋‹น ๋ถ„์•ผ์˜ ์šฉ์–ด๋กœ ํƒ€์ž… ์ด๋ฆ„ ์ง“๊ธฐ Name Types Using the Language of Your Problem Domain

  • (Two Hard Things) There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton

Naming

  • ์ด๋ฆ„ ์ง“๊ธฐ๋Š” ํƒ€์ž… ์„ค๊ณ„์—์„œ ์ค‘์š”ํ•จ
    • ์˜๋„๋ฅผ ๋ช…ํ™•ํžˆ ํ•˜๊ณ , ์ถ”์ƒํ™” ์ˆ˜์ค€์„ ๋†’์ธ๋‹ค
  • ์†์„ฑ์— ๋Œ€ํ•œ ์˜๋ฏธ๋ฅผ ๋ถ„๋ช…ํ•˜๊ฒŒ ํ•˜๊ธฐ
// ๋ชจํ˜ธํ•œ ํƒ€์ž…
interface Animal {
  name: string; // ์ด๋ฆ„์ธ์ง€ ํ•™๋ช…์ธ์ง€?
  endangered: boolean; // ๋ฉธ์ข… / ๋ฉธ์ข… ์œ„๊ธฐ / ๋ฉธ์ข… ์œ„๊ธฐ ์•„๋‹˜
  habitat: string; // ๋ฒ”์œ„๊ฐ€ ๋„ˆ๋ฌด ๋„“๋‹ค. ๋œป์ด ๋ชจํ˜ธํ•จ
}

// ๋ถ„๋ช…ํ•˜๊ฒŒ ํ•˜๊ธฐ
interface Animal {
  commonName: string;
  genus: string;
  species: string;
  status: ConservationStatus;
  climates: KoppenClimate[];
}
type ConservationStatus = "EX" | "EW" | "CR" | "EN" | "VU" | "NT" | "LC";
type KoppenClimate =
  | "Af"
  | "Am"
  | "As"
  | "Aw"
  | "BSh"
  | "BSk"
  | "BWh"
  | "BWk"
  | "Cfa"
  | "Cfb"
  | "Cfc"
  | "Csa"
  | "Csb"
  | "Csc"
  | "Cwa"
  | "Cwb"
  | "Cwc"
  | "Dfa"
  | "Dfb"
  | "Dfc"
  | "Dfd"
  | "Dsa"
  | "Dsb"
  | "Dsc"
  | "Dwa"
  | "Dwb"
  | "Dwc"
  | "Dwd"
  | "EF"
  | "ET";
const snowLeopard: Animal = {
  commonName: "Snow Leopard", // ์ผ๋ฐ˜์ ์ธ ๋ช…์นญ
  genus: "Panthera", // ํ•™๋ช…
  species: "Uncia", // ์ข…
  status: "VU", // ๋™๋ฌผ ๋ณดํ˜ธ ๋“ฑ๊ธ‰์— ๋Œ€ํ•œ IUGN์˜ ํ‘œ์ค€ ๋ถ„๋ฅ˜ ์ฒด๊ณ„์ธ ConservationStatus ํƒ€์ž… (์ทจ์•ฝ์ข… Vulnerable)
  climates: ["ET", "EF", "Dfd"], // ์พจํŽœ ๊ธฐํ›„ ๋ถ„๋ฅ˜ (๊ณ ์‚ฐ๋Œ€ alpine, ์•„๊ณ ์‚ฐ๋Œ€ subalpine)
};

์œ ๋…

  • ์ž์ฒด์ ์œผ๋กœ ์šฉ์–ด๋ฅผ ๋งŒ๋“ค์ง€ ๋ง๊ณ , ํ•ด๋‹น ๋ถ„์•ผ์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์šฉ์–ด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค
    • ์˜ค๋žซ๋™์•ˆ ๋‹ค๋“ฌ์–ด์ ธ ์™”์œผ๋ฉฐ, ํ˜„์žฅ์—์„œ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Œ
    • ์‚ฌ์šฉ์ž์™€์˜ ์†Œํ†ต์— ์œ ๋ฆฌํ•˜๊ณ  ํƒ€์ž…์˜ ๋ช…ํ™•์„ฑ์„ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋‹ค
  • ์ „๋ฌธ ๋ถ„์•ผ์˜ ์šฉ์–ด๋Š” ์ •ํ™•ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

Todo

  • ๋™์ผํ•œ ์˜๋ฏธ๋ฅผ ๋‚˜ํƒ€๋‚ผ ๋•Œ, ๊ฐ™์€ ์šฉ์–ด ์‚ฌ์šฉํ•˜๊ธฐ
  • ๋ชจํ˜ธํ•˜๊ณ  ์˜๋ฏธ ์—†๋Š” ์ด๋ฆ„ ํ”ผํ•˜๊ธฐ
    • ex) data, info, thing, item, object, entity
  • ๋ฐ์ดํ„ฐ ์ž์ฒด๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๊ณ ๋ คํ•˜๊ธฐ
    • ex) INodeLis๊ฐ€ ์•„๋‹ˆ๋ผ Directory. ์ถ”์ƒํ™”์˜ ์ˆ˜์ค€ ๋†’์—ฌ์„œ ์ถฉ๋Œ ์œ„ํ—˜์„ฑ ์ค„์ด๊ธฐ

Things to Remember

  • Reuse names from the domain of your problem where possible to increase the readability and level of abstraction of your code. Make sure you use domain terms accurately.
    • ๊ฐ€๋…์„ฑ๊ณผ ์ถ”์ƒํ™”๋ฅผ ๋†’์ด๋Š” ์ด๋ฆ„์„ ์žฌ์‚ฌ์šฉํ•˜๊ณ , ๋„๋ฉ”์ธ์˜ ์šฉ์–ด๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ
  • Avoid using different names for the same thing: make distinctions in names meaningful.
    • ๊ฐ™์€ ์˜๋ฏธ์— ๋‹ค๋ฅธ ์ด๋ฆ„ ๋ถ™์ด์ง€ ์•Š๊ธฐ. ์šฉ์–ด๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ
  • Avoid vague names like "Info" or "Entity." Name types for what they are, rather than for their shape.
    • ๋ชจํ˜ธํ•œ ์ด๋ฆ„ ๋ถ™์ด์ง€ ์•Š๊ธฐ. ๋ฐ์ดํ„ฐ ํ˜•ํƒœ๋ณด๋‹ค, ๋ฐ์ดํ„ฐ ์ž์ฒด๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๊ณ ๋ คํ•˜๊ธฐ

์•„์ดํ…œ 37: ๊ณต์‹ ๋ช…์นญ์—๋Š” ์ƒํ‘œ๋ฅผ ๋ถ™์ด๊ธฐ Consider Brands for Nominal Typing

  • ๊ตฌ์กฐ์  ํƒ€์ดํ•‘ ํŠน์„ฑ ๋•Œ๋ฌธ์— ์ฝ”๋“œ๊ฐ€ ์ด์ƒํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.
interface Vector2D {
  x: number;
  y: number;
}
function calculateNorm(p: Vector2D) {
  return Math.sqrt(p.x ** 2 + p.y ** 2);
}

calculateNorm({ x: 3, y: 4 }); // OK, result is 5
const vec3D = { x: 3, y: 4, z: 1 };
calculateNorm(vec3D); // OK! result is also 5 // ํ•˜์ง€๋งŒ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋Š” ๊ฒƒ์ด ์ˆ˜ํ•™์ ์œผ๋กœ ์ด์น˜์— ๋งž๋‹ค.

๋ช…๋ชฉ ํƒ€์ž…/๊ณต์‹ ๋ช…์นญ Nominal typing

  • ๊ฐ’์˜ ํƒ€์ž…์ด ๊ตฌ์กฐ๊ฐ€ ์•„๋‹Œ ๋ช…์‹œ์ ์ธ ์„ ์–ธ์— ์˜ํ•ด ๊ฒฐ์ •๋˜๋Š” ์‹œ์Šคํ…œ
    • ๊ตฌ์กฐ์  ํƒ€์ดํ•‘(๊ตฌ์กฐ์— ๋”ฐ๋ผ ํƒ€์ž…์ด ๊ฒฐ์ •๋˜๋Š” ์‹œ์Šคํ…œ)๊ณผ ๋Œ€์กฐ๋œ๋‹ค.

Branding ์ƒํ‘œ ๊ธฐ๋ฒ•

  • _brand, __brand

์˜ˆ์‹œA: ์ƒํ‘œ๋กœ ํƒ€์ž… ์ •์ œํ•˜๊ธฐ

type AbsolutePath = string & { _brand: "abs" };
function listAbsolutePath(path: AbsolutePath) {
  // ...
}
function isAbsolutePath(path: string): path is AbsolutePath {
  return path.startsWith("/");
}

console.log(isAbsolutePath("order/orderId")); // false
console.log(isAbsolutePath("/order/orderId")); // true
  • string์ด๋ฉด์„œ {_brand: 'abs'}๋ฅผ ๊ฐ€์ง€๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜๋Š” ์—†๋‹ค! (์™„์ „ํžˆ ํƒ€์ž… ์‹œ์Šคํ…œ ์˜์—ญ) image
function f(path: string) {
  if (isAbsolutePath(path)) {
    // if ์ฒดํฌ๋กœ ํƒ€์ž…์„ ์ •์ œ refine, ๋‹จ์–ธ๋ฌธ์„ ์ง€์–‘ํ•˜์ž
    listAbsolutePath(path); // AbsolutePath
  }
  listAbsolutePath(path); // AbsolutePath ์•„๋‹˜
  //               ~~~~ Argument of type 'string' is not assignable to
  //                    parameter of type 'AbsolutePath'
}

์˜ˆ์‹œB

  • ๋ชฉ๋ก์—์„œ ํ•œ ์š”์†Œ๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•œ ์ด์ง„ ๊ฒ€์ƒ‰
    • ๋ชฉ๋ก์ด ์ •๋ ฌ๋˜์–ด์•ผ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
function binarySearch<T>(xs: T[], x: T): boolean {
  let low = 0,
    high = xs.length - 1;
  while (high >= low) {
    const mid = low + Math.floor((high - low) / 2);
    const v = xs[mid];
    if (v === x) return true;
    [low, high] = x > v ? [mid + 1, high] : [low, mid - 1];
  }
  return false;
}

// ์ •๋ ฌ๋œ ๋ฆฌ์ŠคํŠธ์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ƒํ‘œ ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•  ๊ฒƒ
type SortedList<T> = T[] & { _brand: "sorted" };

function isSorted<T>(xs: T[]): xs is SortedList<T> {
  for (let i = 0; i < xs.length - 1; i++) {
    if (xs[i] > xs[i + 1]) {
      return false;
    }
  }
  return true;
}

function binarySearch<T>(xs: SortedList<T>, x: T): boolean {
  let low = 0,
    high = xs.length - 1;
  while (high >= low) {
    const mid = low + Math.floor((high - low) / 2);
    const v = xs[mid];
    if (v === x) return true;
    [low, high] = x > v ? [mid + 1, high] : [low, mid - 1];
  }
  return false;
}

const array = [2, 0, 5];
isSorted(array) && binarySearch(array, 1);
binarySearch(array, 1); // ์˜ค๋ฅ˜!

image

์˜ˆ์‹œC: ์ƒํ‘œ ๋ถ™์ด๊ธฐ & ์—ฐ์‚ฐ ํ›„ ์‚ฌ๋ผ์ง€๋Š” ๊ฒฝ์šฐ(์ˆซ์ž)

  • number ํƒ€์ž…์— ์ƒํ‘œ๋ฅผ ๋ถ™์—ฌ๋„, ์‚ฐ์ˆ  ์—ฐ์‚ฐ ํ›„ ์ƒํ‘œ๊ฐ€ ์—†์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•˜๊ธฐ ๋ฌด๋ฆฌ๊ฐ€ ์žˆ๋‹ค.
    • ํ•˜์ง€๋งŒ ํ˜ผํ•ฉ๋œ ๋งŽ์€ ์ˆ˜์˜ number๊ฐ€ ์žˆ์„ ๋•Œ๋Š”, ๋‹จ์œ„๋ฅผ ๋ฌธ์„œํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
type Meters = number & { _brand: "meters" }; // numbe์ธ๋ฐ _brand๊ฐ€ "meters"
type Seconds = number & { _brand: "seconds" }; // numbe์ธ๋ฐ _brand๊ฐ€ "seconds"

const meters = (m: number) => m as Meters;
const seconds = (s: number) => s as Seconds;

const oneKm = meters(1000);
//    ^? const oneKm: Meters
const oneMin = seconds(60);
//    ^? const oneMin: Seconds

const tenKm = oneKm * 10;
//    ^? const tenKm: number
const v = oneKm / oneMin; // ํƒ€์ž… ์—†์–ด์ง
//    ^? const v: number

๋ธŒ๋žœ๋”ฉ์˜ ๋‹ค์–‘ํ•œ ๊ธฐ๋ฒ•

object types

type UserID = { value: number; __brand: "UserID" };
type OrderID = { value: number; __brand: "OrderID" };

function createUserID(value: number): UserID {
  return { value, __brand: "UserID" };
}

function createOrderID(value: number): OrderID {
  return { value, __brand: "OrderID" };
}

const userID: UserID = createUserID(1);
const orderID: OrderID = createOrderID(2);

// ํƒ€์ž… ์˜ค๋ฅ˜: UserID์™€ OrderID๋Š” ์„œ๋กœ ํ˜ธํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
const anotherOrderID: OrderID = userID;

๋ฌธ์ž์—ด ๊ธฐ๋ฐ˜ ์—ด๊ฑฐํ˜• string-based enums

enum Brand {
  UserID = "UserID",
  OrderID = "OrderID",
}

type UserID = { value: number; brand: Brand.UserID };
type OrderID = { value: number; brand: Brand.OrderID };

function createUserID(value: number): UserID {
  return { value, brand: Brand.UserID };
}

function createOrderID(value: number): OrderID {
  return { value, brand: Brand.OrderID };
}

const userID: UserID = createUserID(1);
const orderID: OrderID = createOrderID(2);

// ํƒ€์ž… ์˜ค๋ฅ˜: UserID์™€ OrderID๋Š” ์„œ๋กœ ํ˜ธํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
const anotherOrderID: OrderID = userID;

private fields

class UserID {
  private __brand: "UserID" = "UserID"; // ํด๋ž˜์Šค์˜ ํ”„๋ผ์ด๋น— ํ•„๋“œ ์‚ฌ์šฉ
  constructor(public value: number) {}
}
class OrderID {
  private __brand: "OrderID" = "OrderID";
  constructor(public value: number) {}
}

const userID = new UserID(1);
const orderID = new OrderID(2);

// ํƒ€์ž… ์˜ค๋ฅ˜: UserID์™€ OrderID๋Š” ์„œ๋กœ ํ˜ธํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
const anotherOrderID: OrderID = userID; // ํ”„๋ผ์ด๋น— ํ•„๋“œ๊ฐ€ ์—†์œผ๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ

unique symbols

class UserID {
  private __brand: "UserID" = "UserID";
  constructor(public value: number) {}
}

class OrderID {
  private __brand: "OrderID" = "OrderID";
  constructor(public value: number) {}
}

const userID = new UserID(1);
const orderID = new OrderID(2);

// ํƒ€์ž… ์˜ค๋ฅ˜: UserID์™€ OrderID๋Š” ์„œ๋กœ ํ˜ธํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
const anotherOrderID: OrderID = userID;

Things to Remember

  • With nominal typing, a value has a type because you say it has a type, not because it has the same shape as that type.
    • ๋ช…๋ชฉ ํƒ€์ž… > ์‹ค์ œ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด ์•„๋‹ˆ๋ผ, ํ•ด๋‹น ํƒ€์ž…์ด๋ผ๊ณ  ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ ํƒ€์ž…
    • ๊ตฌ์กฐ์  ํƒ€์ดํ•‘ <-> ๋ช…๋ชฉ ํƒ€์ž…
  • Consider attaching brands to distinguish primitive and object types that are semantically distinct but structurally identical.
    • ์›์‹œํ˜•์ด๋‚˜ ์˜ค๋ธŒ์ ํŠธ ํƒ€์ž…์ด ์˜๋ฏธ๊ฐ€ ๋‹ค๋ฅด์ง€๋งŒ ๊ตฌ์กฐ๊ฐ€ ๋™์ผํ•œ ๊ฒฝ์šฐ, ๋ธŒ๋žœ๋”ฉ์„ ๊ณ ๋ คํ•˜์ž.
    • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ๊ตฌ์กฐ์  ํƒ€์ดํ•‘์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ฐ’์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•˜์ง€ ๋ชปํ•˜๋Š” ์ 
    • ํƒ€์ž… ์‹œ์Šคํ…œ์—์„œ ๋™์ž‘ํ•˜์ง€๋งŒ ๋Ÿฐํƒ€์ž„์— ์ƒํ‘œ๋ฅผ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ํšจ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.
  • Be familiar with the various techniques for branding: properties on object types, string-based enums, private fields, and unique symbols.
    • ๋ธŒ๋žœ๋”ฉ์˜ ๋‹ค์–‘ํ•œ ๊ธฐ๋ฒ•์„ ์•Œ์•„๋‘๊ธฐ