본문으둜 κ±΄λ„ˆλ›°κΈ°

🐀 Chapter 10: μ œλ„€λ¦­ νƒ€μž… μ΄ν•΄ν•˜κΈ°

πŸ¦„ μ œλ„€λ¦­ νƒ€μž… μ΄ν•΄ν•˜κΈ°β€‹

  • μ œλ„€λ¦­ νƒ€μž…μ€ μΈν„°νŽ˜μ΄μŠ€λ‚˜ 클래슀, ν•¨μˆ˜, νƒ€μž… 별칭 등에 μ‚¬μš©ν•  수 μžˆλŠ” κΈ°λŠ₯으둜, ν•΄λ‹Ή μ‹¬λ²Œμ˜ νƒ€μž…μ„ 미리 μ§€μ •ν•˜μ§€ μ•Šκ³  λ‹€μ–‘ν•œ νƒ€μž…μ— λŒ€μ‘ν•˜λ €κ³  ν•  λ•Œ μ‚¬μš©ν•œλ‹€.
// μ œλ„€λ¦­ μΈν„°νŽ˜μ΄μŠ€ ꡬ문
interface IValuable<T> {
value: T;
}

// μ œλ„€λ¦­ ν•¨μˆ˜ ꡬ뢄
function identity<T>(arg: T): T { return arg; }

// μ œλ„€λ¦­ νƒ€μž… λ³„μΉ˜ ꡬ문
type IValuable<T> = {
value: T;
}

// μ œλ„€λ¦­ 클래슀 ꡬ문
class Valuable<T> {
constructor(public value: T) {};
}

πŸ“š μ œλ„€λ¦­ μ‚¬μš©ν•˜κΈ°β€‹

  • μ œλ„€λ¦­ μΈν„°νŽ˜μ΄μŠ€ IValuable<T>λ₯Ό κ΅¬ν˜„ν•˜λŠ” μ œλ„€λ¦­ ν΄λž˜μŠ€λŠ” μžμ‹ μ΄ 가진 νƒ€μž… λ³€μˆ˜ Tλ₯Ό λ‹€μŒμ²˜λŸΌ μΈν„°νŽ˜μ΄μŠ€ μͺ½ μ œλ„€λ¦­ νƒ€μž… λ³€μˆ˜λ‘œ λ„˜κΈΈ 수 μžˆλ‹€.
interface IValuable<T> {
value: T;
}

class Valuable<T> implements IValuable<T> {
constructor(public value: T) {}
}

// μ œλ„€λ¦­ ν•¨μˆ˜λŠ” λ‹€μŒμ²˜λŸΌ μžμ‹ μ˜ νƒ€μž… λ³€μˆ˜ Tλ₯Ό μ œλ„€λ¦­ μΈν„°νŽ˜μ΄μŠ€μ˜ νƒ€μž… λ³€μˆ˜ μͺ½μœΌλ‘œ λ„˜κΈ°λŠ” ν˜•νƒœλ‘œ κ΅¬ν˜„ν•  수 μžˆλ‹€.
const printValue = <T>(o: IValuable<T>): void => console.log(o.value);

printValue(new Valuable<number>(1)); // 1
printValue(new Valuable<boolean>(true)); // true
printValue(new Valuable<string>('hello')); // hello
printValue(new Valuable<number[]>([1, 2, 3])); // [1, 2, 3]

πŸ¦„ μ œλ„€λ¦­ νƒ€μž… μ œμ•½β€‹

  • μ œλ„€λ¦­ νƒ€μž… μ œμ•½μ€ νƒ€μž… λ³€μˆ˜μ— μ μš©ν•  수 μžˆλŠ” νƒ€μž…μ˜ λ²”μœ„λ₯Ό ν•œμ •ν•˜λŠ” κΈ°λŠ₯을 ν•œλ‹€.
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ μ œλ„€λ¦­ ν•¨μˆ˜μ˜ νƒ€μž…μ„ μ œν•œν•˜κ³  싢을 λ•ŒλŠ” λ‹€μŒ ꡬ문을 μ‚¬μš©ν•œλ‹€.
<μ΅œμ’…νƒ€μž…1 extend νƒ€μž…1, μ΅œμ’…νƒ€μž…2 extend νƒ€μž…2>(a: μ΅œμ’…νƒ€μž…1, b: μ΅œμ’…νƒ€μž…2, ...) {}
  • printValueT ν•¨μˆ˜λŠ” μ œλ„€λ¦­ νƒ€μž… μ œμ•½ ꡬ문을 μ‚¬μš©ν•΄ κ΅¬ν˜„ν•˜κ³  μžˆλ‹€.
const printValueT = <Q, T extends IValuable<Q>>(o: T) => console.log(o.value);

printValueT(new Valuable(1)); // 1
printValueT({ value: true }); //true

πŸ“š new νƒ€μž… μ œμ•½β€‹

  • ν”„λ‘œκ·Έλž˜λ° λΆ„μ•Όμ—μ„œ νŒ©ν† λ¦¬ ν•¨μˆ˜λŠ” new μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•΄ 객체λ₯Ό μƒμ„±ν•˜λŠ” κΈ°λŠ₯을 ν•˜λŠ” ν•¨μˆ˜λ₯Ό μ˜λ―Έν•œλ‹€.
  • λ‹€μŒ μ½”λ“œμ—μ„œ create ν•¨μˆ˜μ˜ λ§€κ°œλ³€μˆ˜ type은 μ‹€μ œλ‘œλŠ” νƒ€μž…μ΄λ‹€. λ”°λΌμ„œ type λ³€μˆ˜μ˜ νƒ€μž… μ£Όμ„μœΌλ‘œ λͺ…μ‹œν•œ TλŠ” νƒ€μž…μ˜ νƒ€μž…μ— ν•΄λ‹Ήν•œλ‹€.
const create = <T>(type: T): T => new type();
  • ν•˜μ§€λ§Œ νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ»΄νŒŒμΌλŸ¬λ‚˜λŠ νƒ€μž…μ˜ νƒ€μž…μ„ ν—ˆμš©ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ 였λ₯˜ λ©”μ‹œμ§€κ°€ λ°œμƒν•œλ‹€.
  • κ·Έλž˜μ„œ λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•΄μ€„ 수 μžˆλ‹€.
const create = <T extends { new(): T }>(type: T): T => new type();

// 더 κ°„κ²°ν•œ 문법
const create = <T>(type: new() => T): T => new type();
  • 결둠적으둜, { new(): T }와 new() => TλŠ” 같은 μ˜λ―Έλ‹€. new μ—°μ‚°μžλ₯Ό type에 μ μš©ν•˜λ©΄μ„œ type의 μƒμ„±μž μͺ½μœΌλ‘œ λ§€κ°œλ³€μˆ˜λ₯Ό 전달해야 ν•  λ•Œ λ‹€μŒμ²˜λŸΌ new(...args)ꡬ문을 μ‚¬μš©ν•œλ‹€.
const create = <T>(type: { new(...args): T }, ...args): T => new type(...args);
  • λ‹€μŒ μ½”λ“œλŠ” Point의 μΈμŠ€ν„΄μŠ€λ₯Ό { new(...args): T } νƒ€μž… μ œμ•½μ„ μ„€μ •ν•œ create ν•¨μˆ˜λ‘œ μƒμ„±ν•˜λŠ” μ˜ˆμ΄λ‹€.
class Point {
constructor(public x: number, public y: number) {};
}

[ create(Date), create(Point, 0, 0) ].forEach(s => console.log(s));
// 2020-05-22... Point { x: 0, y: 0 }

πŸ“š 인덱슀 νƒ€μž… μ œμ•½β€‹

  • 객체의 일정 μ†μ„±λ“€λ§Œ μΆ”λ €μ„œ μ’€ 더 λ‹¨μˆœν•œ 객체λ₯Ό λ§Œλ“€μ–΄μ•Ό ν•  λ•Œκ°€ μžˆλ‹€.
const obj = {
name: 'Jane',
age: 22,
city: 'Seoul',
country: 'Korea',
}

const pick = (obj, keys) => keys.map(key => ({ [key]: obj[key] }))
.reduce((result, value) => ({ ...result, ...value }, {}))

// obj κ°μ²΄μ—μ„œ nameκ³Ό age 두 μ†μ„±λ§Œ μΆ”μΆœ
pick(obj, ['name', 'age']); // { name: 'Jane', age: 22 }
pick(obj, ['nam', 'agge']); // { name: undefined, age: undefined }
  • μœ„ 예제처럼 μ˜€νƒ€κ°€ λ°œμƒν•˜λ©΄ μ—‰λš±ν•œ κ²°κ³Όκ°€ λ‚˜μ˜¨λ‹€. νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” μ΄λŸ¬ν•œ 상황을 방지할 λͺ©μ μœΌλ‘œ λ‹€μŒμ²˜λŸΌ keyof T ν˜•νƒœλ‘œ νƒ€μž… μ œμ•½μ„ μ„€μ •ν•  수 있게 μ§€μ›ν•œλ‹€. 이것을 인덱슀 νƒ€μž… μ œμ•½μ΄λΌκ³  ν•œλ‹€.
<T, K extends keyof T>
  • keyof T ꡬ문으둜 νƒ€μž… Kκ°€ νƒ€μž… T의 속성 이름이라고 νƒ€μž… μ œμ•½μ„ μ„€μ •ν•œλ‹€.
const pick = <T, K extends keyof T>(obj: T, keys: K[]) => 
keys.map(key => ({ [key]: obj[key] }))
.reduce((result, value) => ({ ...result, ...value }, {}))
  • μ΄λ ‡κ²Œ ν•˜λ©΄ μ»΄νŒŒμΌμ„ 해보지도 μ•Šκ³  μ•žμ—μ„œ 예둜 λ“  nam, agge와 같은 μž…λ ₯ 였λ₯˜λ₯Ό μ½”λ“œ μž‘μ„± μ‹œμ μ— 탐지할 수 μžˆλ‹€.

πŸ¦„ λŒ€μˆ˜ 데이터 νƒ€μž…β€‹

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ λŒ€μˆ˜ 데이터 νƒ€μž…μ€ 합집합 νƒ€μž…κ³Ό ꡐ집합 νƒ€μž… 두 가지 μ’…λ₯˜κ°€ μžˆλ‹€.

πŸ“š 합집합 νƒ€μž…β€‹

  • 합집합 νƒ€μž…μ€ *λ˜λŠ”(or)*의 의미인 | 기호둜 λ‹€μ–‘ν•œ νƒ€μž…μ„ μ—°κ²°ν•΄μ„œ λ§Œλ“  νƒ€μž…μ„ λ§ν•œλ‹€.
  • λ‹€μŒ μ½”λ“œμ—μ„œ λ³€μˆ˜ ns의 νƒ€μž…μΈ NumberOrString은 numberλ‚˜ string νƒ€μž…μ΄λ―€λ‘œ, 1κ³Ό 같은 μˆ˜μ™€ hello와 같은 λ¬Έμžμ—΄μ„ λͺ¨λ‘ 담을 수 μžˆλ‹€.
type NumberOrString = number | string;
let ns: NumberOrString = 1;
ns = 'hello';

πŸ“š ꡐ집합 νƒ€μž…β€‹

  • ꡐ집합 νƒ€μž…μ€ *이고(and)*의 의미인 & 기호둜 λ‹€μ–‘ν•œ νƒ€μž…μ„ μ—°κ²°ν•΄μ„œ λ§Œλ“  νƒ€μž…μ„ λ§ν•œλ‹€.
  • λŒ€ν‘œμ μΈ 예둜 두 개의 객체λ₯Ό ν†΅ν•©ν•΄μ„œ μƒˆλ‘œμš΄ 객체λ₯Ό λ§Œλ“œλŠ” 것이닀.
const mergeObjects = <T, U>(a: T, b: U): T & U => ({ ...a, ...b });

type INameable = { name: string };
type IAgeable = { age: number };

const nameAndAge: INameable & IAgeable = mergeObjects({ name: 'Jack' }, { age: 32 });
console.log(nameAndAge); // { name: 'Jack', age: 32 }

πŸ“š 식별 합집합 ꡬ문​

  • 식별 합집함 ꡬ문을 μ‚¬μš©ν•˜λ €λ©΄ 합집합 νƒ€μž…μ„ κ΅¬μ„±ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€λ“€μ΄ λͺ¨λ‘ λ˜‘κ°™μ€ μ΄λ¦„μ˜ 속성을 가지고 μžˆμ–΄μ•Ό ν•œλ‹€.
  • λ‹€μŒ μ½”λ“œμ—μ„œ ISquare, IRectangle, ICircle은 λͺ¨λ‘ tagλΌλŠ” μ΄λ¦„μ˜ 곡톡 속성이 μžˆλ‹€.
interface ISquare { tag: 'square', size: number }
interface IRectangle { tag: 'rectangle', width: number, height: number }
interface ICircle { tag: 'circle', radius: number }

type IShape = ISquare | IRectangle | ICircle

const calcArea = (shape: IShape): number => {
switch(shape.tag) {
case 'square': return shape.size * shape.size;
case 'rectangle': return shape.width * shape.height;
case 'circle': return Math.PI * shape.radius * shape.radius;
}

return 0;
}

πŸ¦„ νƒ€μž… κ°€λ“œβ€‹

  • λ‹€μŒ μ½”λ“œμ—μ„œ flyOrSwim ν•¨μˆ˜λŠ” λ§€κ°œλ³€μˆ˜ oκ°€ Birdμ΄κ±°λ‚˜ Fishμ΄λ―€λ‘œ μ½”λ“œ μž‘μ„±μ΄ λͺ¨ν˜Έν•΄μ§ˆ 수 μžˆλ‹€.
  • 즉, ꡬ체적으둜 Bird인지 Fish인지 μ•Œμ•Όν•œλ‹€.
class Bird { fly() { console.log("I'm flying."); }}
class Fish { swim() { console.log("I'm swimming."); }}

const flyOrSwim = (o: Bird | Fish): void => {
// o.fly() ???
}

πŸ“š νƒ€μž… κ°€λ“œβ€‹

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ instanceof μ—°μ‚°μžλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈμ™€ λ‹€λ₯΄κ²Œ νƒ€μž… κ°€λ“œ κ°€λŠ₯이 μžˆλ‹€.
  • μ—¬κΈ°μ„œ νƒ€μž… κ°€λ“œλŠ” νƒ€μž…μ„ λ³€ν™˜ν•˜μ§€ μ•Šμ€ μ½”λ“œ λ•Œλ¬Έμ— ν”„λ‘œκ·Έλž¨μ΄ λΉ„μ •μƒμ μœΌλ‘œ μ’…λ£Œλ˜λŠ” 상황을 λ³΄ν˜Έν•΄μ€€λ‹€λŠ” μ˜λ―Έλ‹€.
const flyOrSwim = (o: Bird | Fish): void => {
if (o instanceof Bird) {
o.fly();
} else if (o instanceof Fish) {
o.swim();
}
}

πŸ“š is μ—°μ‚°μžλ₯Ό ν™œμš©ν•œ μ‚¬μš©μž μ •μ˜ νƒ€μž… κ°€λ“œ ν•¨μˆ˜ μ œμž‘β€‹

  • νƒ€μž… κ°€λ“œ κΈ°λŠ₯을 ν•˜λŠ” ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν•  수 μžˆλ‹€. μ΄λ•Œ ν•¨μˆ˜μ˜ λ°˜ν™˜ νƒ€μž… 뢀뢄에 isλΌλŠ” μ΄λ¦„μ˜ μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.
const isFlyable = (o: Bird | Fish): o is Bird => {
return o instanceof Bird;
}

const isSWimmalbe = (o: Bird | Fish): o is Fish => {
return o instanceof Fish;
}

const swimOfFly = (o: Fish | Bird) => {
if (isSwimmable(o)) {
o.swim();
} else if (isFlyable(o)) {
o.fly();
}
}

[new Bird, new Fish].forEach(swimOfFly); // I'm flying. I'm swimming

πŸ¦„ F-λ°”μš΄λ“œ λ‹€ν˜•μ„±β€‹

πŸ“š this νƒ€μž…κ³Ό F-λ°”μš΄λ“œ λ‹€ν˜•μ„±β€‹

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ this ν‚€μ›Œλ“œλŠ” νƒ€μž…μœΌλ‘œλ„ μ‚¬μš©λœλ‹€.
  • thisκ°€ νƒ€μž…μœΌλ‘œ μ‚¬μš©λ˜λ©΄ 객체지ν–₯ μ–Έμ–΄μ—μ„œ μ˜λ―Έν•˜λŠ” λ‹€ν˜•μ„± νš¨κ³Όκ°€ λ‚˜λŠ”λ°, 일반적인 λ‹€ν˜•μ„±κ³Ό κ΅¬λΆ„ν•˜κΈ° μœ„ν•΄ this νƒ€μž…μœΌλ‘œ μΈν•œ λ‹€ν˜•μ„±μ„ F-λ°”μš΄λ“œ λ‹€ν˜•μ„±μ΄λΌκ³  ν•œλ‹€.

🎈 F-λ°”μš΄λ“œ νƒ€μž…β€‹

  • F-λ°”μš΄λ“œ νƒ€μž…μ΄λž€, μžμ‹ μ„ κ΅¬ν˜„ν•˜κ±°λ‚˜ μƒμ†ν•˜λŠ” μ„œλΈŒνƒ€μž…μ„ ν¬ν•¨ν•˜λŠ” νƒ€μž…μ„ λ§ν•œλ‹€.
  • λ‹€μŒ IAddable<T>λŠ” add λ©”μ„œλ“œκ°€ λ‚΄κ°€ μ•„λ‹Œ λ‚˜λ₯Ό μƒμ†ν•˜λŠ” νƒ€μž…μ„ λ°˜ν™˜ν•˜λŠ” F-λ°”μš΄λ“œ νƒ€μž…μ΄λ‹€.
interface IAddable<T> {
add(value: T): this
}

interface IMultiplyable<T> {
multiply(value: T): this
}

🎈 IValueProvider<T> μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„β€‹

  • λ‹€μŒ Calculator ν΄λž˜μŠ€λŠ” IValueProvider<T> μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆλ‹€.
  • 이 ν΄λž˜μŠ€λŠ” _value 속성을 private으둜 λ§Œλ“€μ–΄ Calculatorλ₯Ό μ‚¬μš©ν•˜λŠ” μ½”λ“œμ—μ„œ _value속성이 μ•„λ‹Œ value() λ©”μ„œλ“œλ‘œ μ ‘κ·Όν•  수 있게 섀계됐닀.
import { IValueProvider } from '../interfaces';

export class Calculator implements IValueProvider<number> {
constructor(private _value: number = 0) {}
value(): number { return this._value };
}
  • 같은 λ°©μ‹μœΌλ‘œ λ‹€μŒ StringComposer λ˜ν•œ IValueProvider<T>λ₯Ό κ΅¬ν˜„ν•œλ‹€.
import { IValueProvider } from '../interfaces';

export class StringComposer implements IValueProvider<string> {
constructor(private _value: string = '') {}
value(): string { return this._value };
}

🎈 IAddable<T>와 IMultiplyable<T> μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„β€‹

  • Calculator의 add λ©”μ„œλ“œλŠ” 클래슀의 this값을 λ°˜ν™˜ν•˜λŠ”λ°, μ΄λŠ” λ©”μ„œλ“œ 체인 κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„œμ΄λ‹€.
import { IValueProvider, IAddable } from '../interfaces';

export class Calculator implements IValueProvider<number>, IAddable<number> {
constructor(private _value: number = 0) {}
value(): number { return this._value };
add(value: number): this {
this._value = this._value + value;
return this;
}
}
  • IMultiplyable<T>도 같은 λ°©λ²•μœΌλ‘œ Calculator ν΄λž˜μŠ€μ— κ΅¬ν˜„ν•œλ‹€.
import { IValueProvider, IAddable, IMultiplyable } from '../interfaces';

export class Calculator implements IValueProvider<number>, IAddable<number>, IMultiplyable<number> {
constructor(private _value: number = 0) {}
value(): number { return this._value };
add(value: number): this {
this._value = this._value + value;
return this;
}
multiply(value: number): this {
this._value = this._value * value;
return this;
}
}
  • λ‹€μŒμ€ Calculator 클래슀λ₯Ό ν…ŒμŠ€νŠΈν•˜λŠ” μ½”λ“œμ΄λ‹€.
import { Calculator } from '../classes/Calculator';

const value = (new Calculator(1))
.add(2) // 3
.add(3) // 6
.multiply(4) // 24
.value()

console.log(value); // 24
  • StringComposer도 Calculatorλ₯Ό κ΅¬ν˜„ν•œ 방식을 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•΄ κ΅¬ν˜„ν•  수 μžˆλ‹€.
import { IValueProvider, IAddable, IMultiplyable } from '../interfaces';

export class StringComposer implements IValueProvider<string>, IAddable<string>, IMultiplyable<number> {
constructor(private _value: string = '') {}
value(): string { return this._value };
add(value: string): this {
this._value = this._value.concat(value);
return this;
}
multiply(repeat: number): this {
const value = this.value();
for (let index = 0; index < repeat; index++) {
this.add(value);
}
return this;
}
}

// StringComposer-test.ts
import { StringComposer } from '../classes/StringComposer';

const value = new StringComposer('hello')
.add(' ') // hello
.add('world') // hello world
.add('!') // hello world!
.multiply(3) // hello world!hello world!hello world!hello world!
.value();

console.log(value); // hello world!hello world!hello world!hello world!
  • λ°˜ν™˜ νƒ€μž… thisλŠ” μ–΄λ–€ λ•ŒλŠ” Calculatorκ°€ λ˜κΈ°λ„ ν•˜κ³  μ–΄λ–€ λ•ŒλŠ” StringComposerκ°€ λ˜κΈ°λ„ν•œλ‹€.
  • 이런 λ°©μ‹μœΌλ‘œ λ™μž‘ν•˜λŠ” 것을 F-λ°”μš΄λ“œ λ‹€ν˜•μ„±μ΄λΌκ³  ν•œλ‹€.

πŸ¦„ nullable νƒ€μž…κ³Ό ν”„λ‘œκ·Έλž¨ μ•ˆμ „μ„±β€‹

πŸ“š nullable νƒ€μž…μ΄λž€?​

  • μžλ°”μŠ€ν¬λ¦½νŠΈμ™€ νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” undefined와 사싀상 같은 의미인 null이 μžˆλ‹€. νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” μ„œλ‘œ ν˜Έν™˜λœλ‹€.
  • undefined와 null νƒ€μž…μ„ nullable νƒ€μž…μ΄λΌκ³  ν•˜λ©°, μ½”λ“œλ‘œλŠ” λ‹€μŒμ²˜λŸΌ ν‘œν˜„ν•  수 μžˆλ‹€.
type nullable = undefined | null
const nullable: nullable = undefined;
  • 이 nullable νƒ€μž…λ“€μ€ ν”„λ‘œκ·Έλž¨μ΄ λ™μž‘ν•  λ•Œ ν”„λ‘œκ·Έλž¨μ„ λΉ„μ •μƒμœΌλ‘œ μ’…λ£Œμ‹œν‚€λŠ” μ£Όμš”μ›μΈμ΄ λœλ‹€.
  • 즉, ν”„λ‘œκ·Έλž¨μ˜ μ•ˆμ „μ„±μ„ ν•΄μΉœλ‹€. ν•¨μˆ˜ν˜• 언어듀은 이λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ μ—°μ‚°μžλ‚˜ 클래슀λ₯Ό μ œκ³΅ν•˜κΈ°λ„ ν•œλ‹€.

πŸ“š μ˜΅μ…˜ 체이닝 μ—°μ‚°μžβ€‹

  • λ³€μˆ˜κ°€ μ„ μ–Έλ§Œ λ˜μ—ˆμ„ 뿐 μ–΄λ–€ κ°’μœΌλ‘œ μ΄ˆκΈ°ν™”λ˜μ§€ μ•ŠμœΌλ©΄ μ½”λ“œλ₯Ό μž‘μ„±ν•  λ•ŒλŠ” λ¬Έμ œκ°€ μ—†μ§€λ§Œ, μ‹€μ œλ‘œ μ‹€ν–‰ν•˜λ©΄(λŸ°νƒ€μž„) 였λ₯˜κ°€ λ°œμƒν•˜λ©΄μ„œ ν”„λ‘œκ·Έλž¨μ΄ λΉ„μ •μƒμœΌλ‘œ μ’…λ£Œν•œλ‹€.
  • 이런 였λ₯˜λŠ” μ•ˆμ „μ„±μ„ ν•΄μΉ˜λ―€λ‘œ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄ μ„€κ³„μžλ“€μ€ μ˜΅μ…˜ 체이닝 μ—°μ‚°μžμ™€ 널 병합 μ—°μ‚°μžλ₯Ό μ œκ³΅ν•œλ‹€.
interface IPerson {
name: string
age?: number
}

let person: IPerson;

console.log(person?.name) // λŸ°νƒ€μž„ 였λ₯˜ 없이 μ •μƒμ μœΌλ‘œ μ‹€ν–‰λ˜λ©°, undefined값이 λ°˜ν™˜λœλ‹€.

πŸ“š 널 병합 μ—°μ‚°μžβ€‹

  • λ‹€μŒ μ½”λ“œλŠ” μ˜΅μ…˜ 체이닝 μ—°μ‚°μžμ™€ 널 병합 μ—°μ‚°μžλ₯Ό ν•œκΊΌλ²ˆμ— μ‚¬μš©ν•˜λŠ”λ°, μ˜΅μ…˜ 체이닝 μ—°μ‚°μž 뢀뢄이 undefinedκ°€ 되면 널 병합 μ—°μ‚°μžκ°€ λ™μž‘ν•΄ undefined λŒ€μ‹  0을 λ°˜ν™˜ν•œλ‹€.
type ICoordinates = { longitude: number }
type ILocation = { country: string, coords: ICoordinates }
type IPerson = { name: string, location: ILocation }

let person: IPerson;

let longitude = person?.location?.coords?.longitude ?? 0;
console.log(longitude); // 0

πŸ“š nullable νƒ€μž…μ˜ ν•¨μˆ˜ν˜• 방식 κ΅¬ν˜„β€‹

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ–Έμ–΄λ‘œ Option νƒ€μž…μ„ κ΅¬ν˜„ν•΄λ³Έλ‹€.
  • λ‹€μŒ μ½”λ“œμ—μ„œ Option ν΄λž˜μŠ€λŠ” μŠ€μΉΌλΌμ—μ„œ μ‚¬μš©λ˜λŠ” λ°©μ‹μœΌλ‘œ λ™μž‘ν•œλ‹€.
import { Some } from './Some';
import { None } from './None';

export class Option {
private constructor() {}
static Some<T>(value: T) { return new Some<T>(value); }
static None = new None();
}

export { Some, None };
  • Option ν΄λž˜μŠ€λŠ” μƒμ„±μžκ°€ private으둜 μ„ μ–Έλ˜μ—ˆμœΌλ―€λ‘œ, new μ—°μ‚°μžλ‘œ Option 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€ 수 μ—†λ‹€.
  • 즉, Option νƒ€μž… κ°μ²΄λŠ” λ‹€μŒμ²˜λŸΌ Option.Some(κ°’) ν˜Ήμ€ Option.None ν˜•νƒœλ‘œλ§Œ 생성할 수 μžˆλ‹€.
  • Someκ³Ό None은 λ‘˜ λ‹€ IValuable<T>와 IFunctor<T>λΌλŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆλŠ”λ°, 두 ν΄λž˜μŠ€λŠ” 각기 λ‹€λ₯Έ 이 μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œλ‹€.
  • IValuable을 κ΅¬ν˜„ν•˜λŠ” Someκ³Ό None은 이 getOrElse λ©”μ„œλ“œλ₯Ό λ°˜λ“œμ‹œ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.
export interface IValuable<T> {
getOrElse(defaultValue: T)
}
  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μ—μ„œλŠ” mapμ΄λΌλŠ” λ©”μ„œλ“œκ°€ μžˆλŠ” νƒ€μž…λ“€μ„ νŽ‘ν„°λΌκ³  λΆ€λ₯Έλ‹€. λ‹€μŒμ€ νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ–Έμ–΄λ‘œ μ„ μ–Έν•œ νŽ‘ν„° μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€. Someκ³Ό None ν΄λž˜μŠ€λŠ” IValuable은 λ¬Όλ‘  이 IFunctor μΈν„°νŽ˜μ΄μŠ€λ„ κ΅¬ν˜„ν•˜κ³  μžˆμœΌλ―€λ‘œ, 이 두 ν΄λž˜μŠ€λŠ” getOrElse와 mapμ΄λΌλŠ” μ΄λ¦„μ˜ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€.
export interface IFunctor<T> {
map<U>(fn: (value: T) => U)
}

🎈 Some 클래슀 κ΅¬ν˜„β€‹

  • value 속성은 private으둜 μ„ μ–Έλ˜μ–΄ μžˆμœΌλ―€λ‘œ Some 클래슀의 μ‚¬μš©μžλŠ” 항상 getOrElse λ©”μ„œλ“œλ₯Ό 톡해 Some ν΄λž˜μŠ€μ— λ‹΄κΈ΄ 값을 μ–»μ–΄μ•Ό ν•œλ‹€. λ˜ν•œ value값을 λ³€κ²½ν•˜λ €λ©΄ 항상 map λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•΄μ•Όλ§Œ ν•œλ‹€.
import { IValuable } from './IValuable';
import { IFunctor } from './IFunctor';

export class Some<T> implements IValuable<T>, IFunctor<T> {
constructor(private value: T) {}

getOrElse(defaultValue: T) {
return this.value ?? defaultValue;
}
map<U>(fn: (T) => U) {
return new Some<U>(fn(this.value));
}
}

🎈 None 클래슀 κ΅¬ν˜„β€‹

  • λ‹€μŒμ€ None 클래슀의 κ΅¬ν˜„ λ‚΄μš©μ΄λ‹€.
import { IValuable } from "./IValuable";
import { nullable } from "./nullable";
import { IFunctor } from './IFunctor';

export class None implements IValuable<nullable>, IFunctor<nullable> {
getOrElse<T>(defaultValue: T | nullable) {
return defaultValue;
}
map<U>(fn: (T) => U) {
return new None;
}
}

🎈 Someκ³Ό None 클래슀 μ‚¬μš©β€‹

import { Option } from '../option/Option';

let m = Option.Some(1);
let value = m.map((value) => value + 1).getOrElse(1);
console.log(value); // 2

let n = Option.None;
value = n.map((value) => value + 1).getOrElse(0)
console.log(value); // 0

πŸ“š Option νƒ€μž…κ³Ό μ˜ˆμ™Έ μ²˜λ¦¬β€‹

  • Option νƒ€μž…μ€ λΆ€μˆ˜ νš¨κ³Όκ°€ μž‡λŠ” 뢈순 ν•¨μˆ˜λ₯Ό 순수 ν•¨μˆ˜λ‘œ λ§Œλ“œλŠ” 데 νš¨κ³Όμ μ΄λ‹€.
  • λ‹€μŒ parseNumber ν•¨μˆ˜λŠ” parseInt의 λ°˜ν™˜κ°’μ΄ NaN인지에 따라 Option.Noneμ΄λ‚˜ Option.Some νƒ€μž…μ˜ 값을 λ°˜ν™˜ν•œλ‹€.
import { IFunctor } from "./IFunctor";
import { IValuable } from "./IValuable";
import { Option } from "./Option";

export const perseNumber = (n: string): IFunctor<number> & IValuable<number> => {
const value = parseInt(n);
return isNaN(value) ? Option.None : Option.Some(value);
}
  • λ‹€μŒ ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” κ°‘μ‹± μ •μƒμ μœΌλ‘œ λ³€ν™˜λ˜λ©΄ map λ©”μ„œλ“œκ°€ λ™μž‘ν•΄ 4κ°€ μΆœλ €λ˜μ§€λ§Œ, 값이 비정상적이면 getOrElse(0)κ°€ μ œκ³΅ν•˜λŠ” 0을 좜λ ₯ν•œλ‹€.
import { parseNumber } from '../option/parseNumber';

let value = parseNumber('1')
.map((value) => value + 1) // 2
.map((value) => value * 2) // 4
.getOrElse(0);

console.log(value);

value = parseNumber('hello world')
.map((value) => value + 1) // 콜백 ν•¨μˆ˜κ°€ ν˜ΈμΆœλ˜μ§€ μ•ŠλŠ”λ‹€
.map((value) => value * 2) // 콜백 ν•¨μˆ˜κ°€ ν˜ΈμΆœλ˜μ§€ μ•ŠλŠ”λ‹€
.getOrElse(0); // 0

console.log(value); // 0
  • μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ JSON.parse ν•¨μˆ˜λŠ” λ§€κ°œλ³€μˆ˜κ°€ 정상적인 JSON 포맷 λ¬Έμžμ—΄μ΄ μ•„λ‹ˆλ©΄ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚¨λ‹€.
  • μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚€λŠ” ν•¨μˆ˜λŠ” λΆ€μˆ˜ νš¨κ³Όκ°€ μžˆλŠ” 뢈순 ν•¨μˆ˜μ΄μ§€λ§Œ, λ‹€μŒ parseJson ν•¨μˆ˜λŠ” try/catch ꡬ문과 Option을 ν™œμš©ν•΄ 순수 ν•¨μˆ˜κ°€ λ˜μ—ˆλ‹€.
import { IValuable } from './IValuable';
import { IFunctor } from './IFunctor';
import { Option } from './Option';

export const parseJson = <T>(json: string): IValuable<T> & IFunctor<T> => {
try {
const value = JSON.parse(json);
return Option.Some<T>(value);
} catch (error) {
return Option.None;
}
}
  • λ‹€μŒ ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” λΉ„μ •μƒμ μœΌλ‘œ μ’…λ£Œν•˜μ§€ μ•Šκ³  μ •μƒμ μœΌλ‘œ λ™μž‘ν•œλ‹€.
import { parseJson } from '../option/parseJson';

const json = JSON.stringify({
name: 'Jack',
age: 32,
});

let value = parseJson(json).getOrElse({});
console.log(value); // { name: 'Jack', age: 32 }

value = parseJson('hello world').getOrElse({});
console.log(value); // {}