diff --git a/Aug/article/Leveraging-TypeScript-branded-types-for-stronger-type-checks.md b/Aug/article/Leveraging-TypeScript-branded-types-for-stronger-type-checks.md new file mode 100644 index 0000000..150f714 --- /dev/null +++ b/Aug/article/Leveraging-TypeScript-branded-types-for-stronger-type-checks.md @@ -0,0 +1,284 @@ +## ๐Ÿ”— [Leveraging TypeScript branded types for stronger type checks](https://blog.logrocket.com/leveraging-typescript-branded-types-stronger-type-checks/) + +### ๐Ÿ—“๏ธ ๋ฒˆ์—ญ ๋‚ ์งœ: 2024.08.04 + +### ๐Ÿงš ๋ฒˆ์—ญํ•œ ํฌ๋ฃจ: ๋ ›์„œ(๊น€๋‹ค์€) + +--- + +## TypeScript ๋ธŒ๋žœ๋“œ ํƒ€์ž…์„ ํ™œ์šฉํ•˜์—ฌ ๋” ๊ฐ•๋ ฅํ•œ ํƒ€์ž… ๊ฒ€์‚ฌํ•˜๊ธฐ + +TypeScript์˜ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์€ ๋” ๋ช…ํ™•ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ๋” ํƒ€์ž… ์•ˆ์ „ํ•œ ์†”๋ฃจ์…˜์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ์ด ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์€ ๊ตฌํ˜„ํ•˜๊ธฐ๋„ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๋ฉฐ, ์ฝ”๋“œ ์œ ์ง€ ๋ณด์ˆ˜๋ฅผ ๋” ํšจ์œจ์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค. + +
+ +![](https://blog.logrocket.com/wp-content/uploads/2024/07/Leveraging-TypeScript-branded-types-stronger-type-checks.png) + +
+ +์ด ๊ธฐ์‚ฌ์—์„œ๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์—ฌ ๋ช‡ ๊ฐ€์ง€ ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ์ด๋ฅด๊ธฐ๊นŒ์ง€, TypeScript ์ฝ”๋“œ์—์„œ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์„ ํšจ๊ณผ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›Œ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์‹œ์ž‘ํ•ด๋ด…์‹œ๋‹ค. + +## TypeScript์˜ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”? + +TypeScript์˜ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์€ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ, ๋งฅ๋ฝ, ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•œ ๋งค์šฐ ๊ฐ•๋ ฅํ•˜๊ณ  ํšจ์œจ์ ์ธ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ด ํƒ€์ž…์€ ๊ธฐ์กด ํƒ€์ž…์— ์ถ”๊ฐ€ ์ •์˜๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๊ตฌ์กฐ์™€ ํŒŒ์ผ ์ด๋ฆ„์ด ์œ ์‚ฌํ•œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฌธ์ž์—ด๋กœ ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ์„ ์ €์žฅํ•˜๋Š” ๋Œ€์‹ , ์ด๋ฉ”์ผ ์ฃผ์†Œ์— ๋Œ€ํ•œ ๋ธŒ๋žœ๋“œ TypeScript ํƒ€์ž…์„ ๋งŒ๋“ค์–ด ์ผ๋ฐ˜ ๋ฌธ์ž์—ด๊ณผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋” ์ฒด๊ณ„์ ์œผ๋กœ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๊ณ  ์ฝ”๋“œ๋„ ๋ช…ํ™•ํ•ด์ง‘๋‹ˆ๋‹ค. + +๋ธŒ๋žœ๋“œ ํƒ€์ž… ์—†์ด ์šฐ๋ฆฌ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์ฒ˜๋Ÿผ ๊ฐ’๋“ค์„ ์ œ๋„ค๋ฆญ ํƒ€์ž… ๋ณ€์ˆ˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ๋ธŒ๋žœ๋“œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น ๋ณ€์ˆ˜๋ฅผ ๊ฐ•์กฐํ•˜๊ณ  ์ฝ”๋“œ ์ „๋ฐ˜์—์„œ ๊ทธ ์œ ํšจ์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ๊ฐ„๋‹จํ•œ TypeScript ๋ธŒ๋žœ๋“œ ํƒ€์ž… ์˜ˆ์ œ + +์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์œ„ํ•œ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์„ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค. type์— ๋ธŒ๋žœ๋“œ ์ด๋ฆ„์„ ๋ถ™์ด๋Š” ๊ฒƒ์œผ๋กœ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ์ด๋ฉ”์ผ ์ฃผ์†Œ์— ๋Œ€ํ•œ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +```ts +type EmailAddress = string & { __brand: 'EmailAddress' }; +``` + +์—ฌ๊ธฐ์„œ, ์šฐ๋ฆฌ๋Š” \_\_brand ์ด๋ฆ„ 'EmailAddress'๋ฅผ ๋ถ™์—ฌ์„œ ๋ธŒ๋žœ๋“œ ํƒ€์ž… EmailAddress๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. + +๋ธŒ๋žœ๋“œ ํƒ€์ž…์„ ๋งŒ๋“ค ๋•Œ ์ œ๋„ค๋ฆญ ๋ฌธ๋ฒ•์€ ํ•„์š”์—†๋‹ค๋Š” ์‚ฌ์‹ค์„ ๋ช…์‹ฌํ•˜์„ธ์š”. ๋ธŒ๋žœ๋“œ ํƒ€์ž…์˜ ์ œ๋„ค๋ฆญ(๋ฌธ์ž์—ด, ์ˆซ์ž ๋“ฑ)์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. \_\_brand ๋ฌธ๋ฒ•๋„ ์˜ˆ์•ฝ์–ด๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ \_\_brand ์™ธ์˜ ๋‹ค๋ฅธ ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด์ œ EmailAddress ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ๋ฌธ์ž์—ด์„ ์ „๋‹ฌํ•ด ๋ด…์‹œ๋‹ค: + +```ts +const email: EmailAddress = 'asd'; // error +``` + +๋ณด์‹œ๋Š” ๋ฐ”์™€ ๊ฐ™์ด ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. + +```shell +Type 'string' is not assignable to type 'EmailAddress.' Type 'string' is not assignable to type '{ __brand: "EmailAddress"; }.' +``` + +์ด๋ฅผ ๊ณ ์น˜๊ธฐ ์œ„ํ•ด์„œ ์ด๋ฉ”์ผ ์ฃผ์†Œ์— ๋Œ€ํ•œ ๊ธฐ๋ณธ์ ์ธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ƒ์„ฑํ•ด๋ด…์‹œ๋‹ค. + +```ts +const isEmailAddress = (email: string): email is EmailAddress => { + return email.endsWith('@gmail.com'); +}; +``` + +์—ฌ๊ธฐ์„œ boolean ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹ , `email is EmailAddress`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ•จ์ˆ˜๊ฐ€ `true`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด email์ด `EmailAddress` [ํƒ€์ž…์œผ๋กœ ์บ์ŠคํŒ…](https://blog.logrocket.com/how-to-perform-type-casting-typescript/)๋œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ฌธ์ž์—ด์— ๋Œ€ํ•ด ์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ „์— ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +```ts +const sendVerificationEmail = (email: EmailAddress) => { + //... +}; +const signUp = (email: string, password: string) => { + //... + if (isEmailAddress(email)) { + sendVerificationEmail(email); // pass + } + sendVerificationEmail(email); // error +}; +``` + +๋ณด์‹œ๋Š” ๋ฐ”์™€ ๊ฐ™์ด, ์˜ค๋ฅ˜๋Š” if ์กฐ๊ฑด๋ฌธ ์•ˆ์—์„œ ๋ณด์ด์ง€ ์•Š์ง€๋งŒ, ํ•ด๋‹น ์กฐ๊ฑด๋ฌธ์˜ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. + +`assert`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฒ€์ฆ์ด ํ†ต๊ณผ๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ ์ž ํ•  ๋•Œ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +```ts +function assertEmailAddress(email: string): asserts email is EmailAddress { + if (!email.endsWith('@gmail.com')) { + throw new Error('Not an email addres'); + } +} + +const sendVerificationEmail = (email: EmailAddress) => { + //... +}; + +const signUp = (email: string, password: string) => { + //... + assertEmailAddress(email); + sendVerificationEmail(email); // ok +}; +``` + +์—ฌ๊ธฐ์„œ ๋ณด์‹œ๋‹ค์‹œํ”ผ, ๋ฐ˜ํ™˜ ํƒ€์ž…์œผ๋กœ `asserts email is EmailAddress`๋ฅผ ๋ช…์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ฒ€์ฆ์ด ํ†ต๊ณผํ•˜๋ฉด ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ ๋ธŒ๋žœ๋“œ ํƒ€์ž… `EmailAddress`์ž„์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + +## TypeScript์—์„œ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์˜ ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์‚ฌ๋ก€ + +์œ„ ์˜ˆ์‹œ๋Š” ๋ธŒ๋žœ๋“œ ํƒ€์ž…์˜ ๊ฐ„๋‹จํ•œ ์‹œ์—ฐ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด๋ฅผ ๋” ๊ณ ๊ธ‰ ์‚ฌ๋ก€์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์‹œ๋ฅผ ํ•˜๋‚˜ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +๋จผ์ €, ๋‹ค๋ฅธ ํƒ€์ž…์— ๋ถ™์ผ ์ˆ˜ ์žˆ๋Š” ๊ณตํ†ต Branded ํƒ€์ž…์„ ์„ ์–ธํ•ด ๋ด…์‹œ๋‹ค: + +```ts +declare const __brand: unique symbol; +type Brand = { [__brand]: B }; +export type Branded = T & Brand; +``` + +์—ฌ๊ธฐ์„œ, ์‹ฌ๋ณผ์„ ์‚ฌ์šฉํ•ด ๊ฐ ํƒ€์ž…์„ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ณ ์œ ํ•œ ๋ธŒ๋žœ๋“œ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์‹ฌ๋ณผ์€ ๋‹ค๋ฅธ ์–ด๋–ค ์‹ฌ๋ณผ๊ณผ๋„ ๊ตฌ๋ณ„๋˜๋Š” ์œ ์ผํ•œ ์‹ฌ๋ณผ์ž„์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ƒˆ๋กœ์šด ์‹ฌ๋ณผ์„ ์ƒ์„ฑํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ์‹ฌ๋ณผ๊ณผ ๊ตฌ๋ณ„๋œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ด๋ฅผ ์„ค๋ช…ํ•˜๋Š” ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค: + +```ts +// ๋ธŒ๋žœ๋“œ๋กœ ์‚ฌ์šฉํ•  ๊ณ ์œ  ์‹ฌ๋ณผ ์ •์˜ +const metersSymbol: unique symbol = Symbol('meters'); +const kilometersSymbol: unique symbol = Symbol('kilometers'); + +// ๋ธŒ๋žœ๋“œ ํƒ€์ž… ์ •์˜ +type Meters = number & { [metersSymbol]: void }; +type Kilometers = number & { [kilometersSymbol]: void }; + +// ๋ธŒ๋žœ๋“œ ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ๋„์šฐ๋ฏธ ํ•จ์ˆ˜ +function meters(value: number): Meters { + return value as Meters; +} + +function kilometers(value: number): Kilometers { + return value as Kilometers; +} + +// ๋ธŒ๋žœ๋“œ ํƒ€์ž…์„ ๊ฐ€์ง„ ๋ณ€์ˆ˜๋“ค +const distanceInMeters: Meters = meters(100); +const distanceInKilometers: Kilometers = kilometers(1); + +// ์•„๋ž˜ ํ• ๋‹น์€ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ด +const wrongDistance: Meters = distanceInKilometers; +const anotherWrongDistance: Kilometers = distanceInMeters; + +// ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ๋ฒ• +const anotherDistanceInMeters: Meters = meters(200); +const anotherDistanceInKilometers: Kilometers = kilometers(2); + +console.log(distanceInMeters, distanceInKilometers); +``` + +๊ณตํ†ต Branded ํƒ€์ž… ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฐ€์ง€๋ฉด TypeScript์—์„œ ์—ฌ๋Ÿฌ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์„ ๋™์‹œ์— ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด, ์ฝ”๋“œ ๊ตฌํ˜„์„ ์ค„์ด๊ณ  ์ฝ”๋“œ๋ฅผ ํ›จ์”ฌ ๊น”๋”ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์œ„์˜ ์ด๋ฉ”์ผ ๊ฒ€์ฆ ์˜ˆ์ œ๋ฅผ ํ™•์žฅํ•˜์—ฌ, ์ด ๊ณตํ†ต Branded ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด EmailAddress ๋ธŒ๋žœ๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```ts +type EmailAddress = Branded; + +const isEmailAddress = (email: string): email is EmailAddress => { + return email.endsWith('@gmail.com'); +}; + +const sendEmail = (email: EmailAddress) => { + // ... +}; + +const signUp = (email: string, password: string) => { + if (isEmailAddress(email)) { + // ์ธ์ฆ ๋ฉ”์ผ ์ „์†ก + sendEmail(email); + } +}; +``` + +์ด์ œ ์ด Branded ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋ธŒ๋žœ๋“œ TypeScript ํƒ€์ž…์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Branded ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ์˜ˆ๋ฅผ ์‚ดํŽด๋ด…์‹œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒŒ์‹œ๋ฌผ์„ ์ข‹์•„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. userId์™€ postId ๋ชจ๋‘์— Branded ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```ts +type UserId = Branded; +type PostId = Branded; + +type User = { + userId: UserId; + username: string; + email: string; +}; + +type Post = { + postId: PostId; + title: string; + description: string; + likes: Like[]; +}; + +type Like = { + userId: UserId; + postId: PostId; +}; + +const likePost = async (userId: UserId, postId: PostId) => { + const response = await fetch(`/posts/${postId}/like/${userId}`, { + method: 'post', + }); + return await response.json(); +}; + +// ๊ฐ€์ƒ์˜ ๊ฐ์ฒด +const user: User = { + userId: '1' as UserId, + email: 'a@email.com', + username: 'User1', +}; +const post: Post = { + postId: '2' as PostId, + title: 'Sample Title', + description: 'Sample post description', + likes: [], +}; + +likePost(user.userId, post.postId); // ok +likePost(post.postId, user.userId); // error +``` + +## TypeScript 5.5-beta์—์„œ ๋ธŒ๋žœ๋“œ ํƒ€์ž…์œผ๋กœ ์ž‘์—…ํ•˜๊ธฐ + +์ƒˆ๋กœ์šด TypeScript 5.5-beta ๋ฆด๋ฆฌ์Šค์—์„œ๋Š” TypeScript์˜ ์ œ์–ด ํ๋ฆ„ ๋ถ„์„์ด ์ฝ”๋“œ๊ฐ€ ์ง„ํ–‰๋จ์— ๋”ฐ๋ผ ๋ณ€์ˆ˜์˜ ํƒ€์ž…์ด ์–ด๋–ป๊ฒŒ ๋ณ€ํ•˜๋Š”์ง€ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ๋ณ€์ˆ˜์˜ ํƒ€์ž…์ด ์ฝ”๋“œ ๋กœ์ง์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, TypeScript๊ฐ€ ์ฝ”๋“œ ๋กœ์ง์˜ ๊ฐ ์ˆ˜์ • ์ฒด์ธ์—์„œ ๋ณ€์ˆ˜ ํƒ€์ž…์„ ์ถ”์ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋ณ€์ˆ˜๊ฐ€ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ๊ฐ€๋Šฅํ•œ ํƒ€์ž…์ด ์žˆ๋Š” ๊ฒฝ์šฐ, ํ•„์š”ํ•œ ์กฐ๊ฑด์„ ์ ์šฉํ•˜์—ฌ ๋ณ€์ˆ˜์˜ ํƒ€์ž…์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด์„œ ์ดํ•ดํ•ด๋ด…์‹œ๋‹ค: + +```tsx +interface ItemProps { + // ... +} + +declare const items: Map; + +function getItem(id: string) { + const item = items.get(id); // item์€ ItemProps | undefined ํƒ€์ž…์œผ๋กœ ์„ ์–ธ๋จ + if (item) { + // if ๋ฌธ ์•ˆ์—์„œ item์€ ItemProps ํƒ€์ž…์„ ๊ฐ€์ง + } else { + // ์—ฌ๊ธฐ์„œ item์€ undefined ํƒ€์ž…์„ ๊ฐ€์ง + } +} + +function getAllItemsByIds(ids: string[]): ItemProps[] { + return ids.map((id) => items.get(id)).filter((item) => item !== undefined); // ์ด์ „์—๋Š” ์ด๋Ÿฐ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: Type '(ItemProps | undefined)[]' is not assignable to type 'ItemProps[]'. Type 'ItemProps | undefined' is not assignable to type 'ItemProps'. Type 'undefined' is not assignable to type 'ItemProps' +} +``` + +๋‹ค์Œ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” ์ด๋ฉ”์ผ ์ฃผ์†Œ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์™€์„œ ๊ฒ€์ฆ๋œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด์ „ ์˜ˆ์ œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ธŒ๋žœ๋“œ EmailAddress ํƒ€์ž…์„ ์ƒ์„ฑํ•˜๊ณ  ๊ฒ€์ฆ๋œ ์ด๋ฉ”์ผ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```ts +type EmailAddress = Branded; +const isEmailAddress = (email: string): email is EmailAddress => { + return email.endsWith('@gmail.com'); +}; + +const storeToDb = async (emails: EmailAddress[]) => { + const response = await fetch('/store-to-db', { + body: JSON.stringify({ + emails, + }), + method: 'post', + }); + return await response.json(); +}; + +const emails = ['a@gmail.com', 'b@gmail.com', '...']; +const validatedEmails = emails.filter((email) => isEmailAddress(email)); +storeToDb(validatedEmails); // error +``` + +์—ฌ๊ธฐ์„œ ๊ฒ€์ฆ๋œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ validatedEmails ๋ฐฐ์—ด์— ๋‚˜์—ดํ•˜๊ณ  ์žˆ์ง€๋งŒ, storeToDb ํ•จ์ˆ˜์— ์ „๋‹ฌํ•  ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์˜ค๋ฅ˜๋Š” ํŠนํžˆ v5.5-beta ์ด์ „์˜ TypeScript ๋ฒ„์ „์„ ์‚ฌ์šฉํ•  ๋•Œ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. + +๋‚ฎ์€ ๋ฒ„์ „์˜ TypeScript์—์„œ๋Š” validatedEmails ๋ฐฐ์—ด์˜ ํƒ€์ž…์ด ์›๋ž˜ ๋ณ€์ˆ˜์ธ emails ๋ฐฐ์—ด์—์„œ ํŒŒ์ƒ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ validatedEmails ๋ฐฐ์—ด์˜ ํƒ€์ž…์ด string[]๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด ๋ฌธ์ œ๋Š” ํ˜„์žฌ TypeScript ๋ฒ ํƒ€ ๋ฒ„์ „(ํ˜„์žฌ ๊ธฐ์ค€์œผ๋กœ 5.5-beta)์—์„œ ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +ํ˜„์žฌ ๋ฒ ํƒ€ ๋ฒ„์ „์—์„œ๋Š” ๊ฒ€์ฆ๋œ ์ด๋ฉ”์ผ์„ ํ•„ํ„ฐ๋งํ•œ ํ›„์— validatedEmails๊ฐ€ ์ž๋™์œผ๋กœ EmailAddress[]๋กœ ํƒ€์ž… ์บ์ŠคํŒ…๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ TypeScript 5.5-beta ๋ฒ„์ „์—์„œ๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ์— TypeScript ๋ฒ ํƒ€ ๋ฒ„์ „์„ ์„ค์น˜ํ•˜๋ ค๋ฉด ํ„ฐ๋ฏธ๋„์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์‹ญ์‹œ์˜ค: + +```shell +npm install -D typescript@beta +``` + +
+ +## ๊ฒฐ๋ก  + +๋ธŒ๋žœ๋“œ ํƒ€์ž…์€ TypeScript์—์„œ ๋งค์šฐ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ด๋“ค์€ ๋Ÿฐํƒ€์ž„ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์ œ๊ณตํ•˜์—ฌ ์ฝ”๋“œ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•˜๊ณ  ๊ฐ€๋…์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋„๋ฉ”์ธ ์ˆ˜์ค€์—์„œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์ฝ”๋“œ์˜ ๋ฒ„๊ทธ๋ฅผ ์ค„์ด๋Š” ๋ฐ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. + +๋ธŒ๋žœ๋“œ ํƒ€์ž…์„ ์‰ฝ๊ฒŒ ๊ฒ€์ฆํ•˜๊ณ , ๊ฒ€์ฆ๋œ ๊ฐ์ฒด๋ฅผ ํ”„๋กœ์ ํŠธ ์ „๋ฐ˜์— ๊ฑธ์ณ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ตœ์‹  TypeScript ๋ฒ ํƒ€ ๋ฒ„์ „ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋”ฉ ๊ฒฝํ—˜์„ ๋”์šฑ ์›ํ™œํ•˜๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.