๋ฐ˜์‘ํ˜•

Branded Type


ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ ๋‘ ๊ฐ์ฒด์˜ ํŠน์ • ์†์„ฑ์ด ๋™์ผํ•œ ํƒ€์ž…์„ ๊ฐ€์งˆ ๋•Œ๊ฐ€ ๋งŽ๋‹ค. ์ด๋กœ ์ธํ•ด ํƒ€์ž… ์‹œ์Šคํ…œ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋”๋ผ๋„ ๋…ผ๋ฆฌ์  ์˜ค๋ฅ˜๋‚˜ ํƒ€์ž… ์•ˆ์ „์„ฑ ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.

type User = {
  id: string;
  name: string;
};

type Post = {
  id: string;
  ownerId: string;
  comments: Comments[];
};

type Comments = {
  id: string;
  timestamp: string;
  body: string;
  authorId: string;
};

 

์œ„ ์˜ˆ์‹œ์—์„œ User.id, Post.id, Comments.id๋Š” ๋ชจ๋‘ string ํƒ€์ž…์ด๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ธ์ž postId, ๋‘ ๋ฒˆ์งธ ์ธ์ž authorId๋ฅผ ๋ฐ›๋Š” ํ•จ์ˆ˜์— ์ธ์ž ์ˆœ์„œ๋ฅผ ๋ฐ˜๋Œ€๋กœ ์ „๋‹ฌํ•ด๋„, ๋‘ ํƒ€์ž…์ด ๋ฌธ์ž์—ด๋กœ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฅ˜๋ฅผ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•œ๋‹ค. ํƒ€์ž… ์‹œ์Šคํ…œ์—์„  ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜์ง€๋งŒ, ๋Ÿฐํƒ€์ž„์—์„  ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€ id ๊ฐ’ ๋•Œ๋ฌธ์— ์ž˜๋ชป๋œ ์‘๋‹ต์„ ๋ฐ›๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

async function getCommentsForPost(postId: string, authorId: string) {
  const response = await api.get(
    `/author/${authorId}/posts/${postId}/comments`,
  );
  return response.data;
}

const comments = await getCommentsForPost(user.id, post.id); // ํƒ€์ž…์—๋Ÿฌ ๋ฐœ์ƒ ์•ˆํ•จ

 

์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋ธŒ๋žœ๋””๋“œ ํƒ€์ž…(Branded Types)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ธŒ๋žœ๋””๋“œ ํƒ€์ž…์€ ๊ธฐ์กด ํƒ€์ž…์— ๊ณ ์œ ํ•œ ์‹๋ณ„์ž๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ํ”„๋กœํผํ‹ฐ(๋ผ๋ฒจ)๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ณด๋‹ค ๋ช…ํ™•ํ•˜๊ณ  ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์„ ์ƒ์„ฑํ•œ๋‹ค.

type Brand<K, T> = K & { __brand: T };
type UserID = Brand<string, "UserId">;
type PostID = Brand<string, "PostId">;

const userId = "123" as UserID;
console.log(typeof userId); // string
console.log(userId.__brand); // undefined

 

์œ„ ์ฝ”๋“œ์—์„œ Brand<K, T>๋Š” K๋ฅผ ๊ธฐ๋ณธ ํƒ€์ž…์œผ๋กœ, T๋ฅผ ๊ณ ์œ ํ•œ ์‹๋ณ„์ž๋กœ ์‚ฌ์šฉํ•˜์—ฌ ํƒ€์ž…์„ ํ™•์žฅํ•˜๊ณ  ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด K๋ฅผ string์œผ๋กœ ์ง€์ •ํ•˜๋ฉด, string ํƒ€์ž…์— __brand๋ผ๋Š” ํŠน์ˆ˜ ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋ถ™์€ ์ƒˆ๋กœ์šด ํƒ€์ž…์ด ์ƒ์„ฑ๋œ๋‹ค.

 

์ด๋•Œ __brand ํ”„๋กœํผํ‹ฐ๋Š” ๋Ÿฐํƒ€์ž„์—์„  ์กด์žฌํ•˜์ง€ ์•Š๊ณ , ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ๋ธŒ๋žœ๋””๋“œ ํƒ€์ž…์„ ๊ตฌ๋ถ„ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋™์ผํ•œ ์›์‹œ ํƒ€์ž…์ธ string์„ ์‚ฌ์šฉํ•˜๋”๋ผ๋„, ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ํƒ€์ž…์œผ๋กœ ์ธ์‹ํ•˜์—ฌ ํƒ€์ž… ์•ˆ์ „์„ฑ์ด ๊ฐ•ํ™”๋œ๋‹ค.

type Brand<K, T> = K & { __brand: T };

type UserID = Brand<string, "UserId">;
type PostID = Brand<string, "PostId">;
type CommentID = Brand<string, "CommentId">;

type User = {
  id: UserID;
  name: string;
};

type Post = {
  id: PostID;
  ownerId: string;
  comments: Comments[];
};

type Comments = {
  id: CommentID;
  timestamp: string;
  body: string;
  authorId: UserID;
};

async function getCommentsForPost(postId: PostID, authorId: UserID) {
  // ...
}

const user = {} as User;
const post = {} as Post;

// โŒ TS2345: Argument of type UserID is not assignable to parameter of type PostID
const comments = getCommentsForPost(user.id, post.id);

 

๐Ÿ’ก ์ œ๋„ค๋ฆญ์ด ์›์‹œ ํƒ€์ž…์ผ ๋•Œ๋„ ๊ต์ฐจ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์—ฌ(& ์—ฐ์‚ฐ์ž) ํƒ€์ž… ๋ ˆ๋ฒจ์—์„œ ์ถ”๊ฐ€์ ์ธ ์†์„ฑ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋‹ค. ํƒ€์ž…์„ ํ• ๋‹นํ•  ๋•Œ ๊ต์ฐจ ํƒ€์ž… ํŠน์„ฑ์— ๋”ฐ๋ผ ๋ชจ๋“  ํŠน์„ฑ์„ ํฌํ•จํ•œ ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์œผ๋กœ ๊ฒฐ์ •๋˜๋Š”๋ฐ(๋‘ ํƒ€์ž…์„ ๋ชจ๋‘ ๋งŒ์กฑํ•˜๋Š” ํ•˜๋‚˜์˜ ํƒ€์ž… ์ƒ์„ฑ), ์ œ๋„ค๋ฆญ์— ์›์‹œ ํƒ€์ž…์„ ๋„˜๊ฒผ๋‹ค๋ฉด ์ถ”๊ฐ€๋œ ์†์„ฑ์€ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ์œ„ํ•œ ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉ๋˜๊ณ , ๋Ÿฐํƒ€์ž„์—์„  ์›์‹œ ํƒ€์ž…์œผ๋กœ ํ‰๊ฐ€๋œ๋‹ค.

 

 

๊ฐœ์„ ๋œ Branded Type


์œ„์—์„œ ์‚ฌ์šฉํ•œ __brand ํ”„๋กœํผํ‹ฐ๋Š” ๋Ÿฐํƒ€์ž„์—๋Š” ์กด์žฌํ•˜์ง€ ์•Š์ง€๋งŒ, ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ ์ž๋™ ์™„์„ฑ์— ํ‘œ์‹œ๋ผ์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด ๋ณด์ด๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. ์•„๋ž˜์ฒ˜๋Ÿผ ์œ ๋‹ˆํฌ ์‹ฌ๋ณผ์„ ํ™œ์šฉํ•˜๋ฉด ์ค‘๋ณต ํ‚ค ์‚ฌ์šฉ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๊ณ , declare ํ‚ค์›Œ๋“œ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ํƒ€์ž… ์‹œ์Šคํ…œ์—๋งŒ ์กด์žฌํ•˜๋Š” __brand ์‹ฌ๋ณผ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ •์˜ํ•ด์„œ ์ ‘๊ทผ์„ ์ฐจ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค.

declare const __brand: unique symbol;
type Brand<B> = { [__brand]: B };
type Branded<T, B> = T & Brand<B>;

type UserID = Branded<string, "UserId">;

const userId = "123" as UserID;
console.log(typeof userId); // string
console.log(userId.__brand); // TS2339: Property __brand does not exist on type UserID

 

unique symbol์€ ํƒ€์ž… ์‹œ์Šคํ…œ์—์„œ ๊ณ ์œ ์„ฑ์„ ๋”์šฑ ๊ฐ•ํ•˜๊ฒŒ ๋ณด์žฅํ•˜์—ฌ ์‹ฌ๋ณผ ๊ฐ„์˜ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋Šฅ์ด๋‹ค. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ Symbol์€ ๊ณ ์œ ํ•œ ๊ฐ’์„ ๊ฐ€์ง€์ง€๋งŒ, unique symbol์„ ์‚ฌ์šฉํ•˜๋ฉด ํƒ€์ž… ์ˆ˜์ค€์—์„œ์˜ ๊ณ ์œ ์„ฑ๊นŒ์ง€ ์—„๊ฒฉํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. unique symbol์€ const ์„ ์–ธ์ด๋‚˜ ํด๋ž˜์Šค์˜ readonly static ์†์„ฑ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

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

 

 

Use Cases


์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•œ ํ›„, ๋ธŒ๋žœ๋””๋“œ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ผ๋ฐ˜ ๋ฌธ์ž์—ด๊ณผ ๊ตฌ๋ถ„ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ๋” ๊ฐ•ํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

type EmailAddress = Brand<string, "EmailAddress">;
function validEmail(email: string): EmailAddress {
  // ์ด๋ฉ”์ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง
  return email as EmailAddress;
}

 

๋ธŒ๋žœ๋””๋“œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋ฉด ๋” ํ‘œํ˜„๋ ฅ ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜์ฒ˜๋Ÿผ CarBrand, EngineType๊ณผ ๊ฐ™์€ ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์„ ๋„์ž…ํ•จ์œผ๋กœ์จ, ๊ฐ ๊ฐ’๋“ค์ด ์–ด๋–ค ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€๋Š”์ง€ ์ •ํ™•ํ•˜๊ฒŒ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.

type CarBrand = Brand<string, "CarBrand">;
type EngineType = Brand<string, "EngineType">;
type CarModel = Brand<string, "CarModel">;
type CarColor = Brand<string, "CarColor">;

function createCar(
  carBrand: CarBrand,
  carModel: CarModel,
  engineType: EngineType,
  color: CarColor,
): Car {
  // ...
}

// ๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด์€ CarBrand, CarModel, EngineType, CarColor ํƒ€์ž…์ด ์•„๋‹ˆ๋ฏ€๋กœ ์—๋Ÿฌ ๋ฐœ์ƒ
const car = createCar("Toyota", "Corolla", "Diesel", "Red"); // ts2345 Error

 

 

๋ ˆํผ๋Ÿฐ์Šค


 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•