๋ฐ˜์‘ํ˜•

TL;DR


  • parameterName is Type : ์กฐ๊ฑด๋ฌธ์—์„œ ์‚ฌ์šฉ (if๋ฌธ์œผ๋กœ ํƒ€์ž…์„ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌํ•  ๋•Œ ์‚ฌ์šฉ)
  • asserts parameterName is Type : ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ด (์Šค์ฝ”ํ”„ ๋‚ด์—์„œ ์œ ํšจํ•œ ํƒ€์ž…์ธ์ง€ ํŒ๋‹จํ•  ๋•Œ ์‚ฌ์šฉ)

 

is ํ‚ค์›Œ๋“œ


ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์—์„œ ํƒ€์ž… ๋ฒ”์œ„๋ฅผ ์ขํ˜€๋‚˜๊ฐ€๋Š” ์ž‘์—…(๊ธฐ๋Šฅ)์„ ํƒ€์ž…๊ฐ€๋“œ๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. ๊ฐ„๋‹จํ•œ ํƒ€์ž…์€ in, typeof ํ‚ค์›Œ๋“œ ๋“ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ โžŠํƒ€์ž…์ด ๋ณต์žกํ•˜๊ฑฐ๋‚˜ โž‹ํƒ€์ž… ์ฒดํฌ ๋กœ์ง์„ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋• ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž… ๊ฐ€๋“œ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•œ๋‹ค. ํƒ€์ž… ๊ฐ€๋“œ ํ•จ์ˆ˜๋Š” ๋ฆฌํ„ด๋ฌธ์— is ํ‚ค์›Œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ “์–ด๋–ค ์ธ์ž๋Š” ์–ด๋–ค ํƒ€์ž…์ด๋‹ค” ๋ผ๋Š” ๊ฐ’(ํƒ€์ž… ๋ช…์ œ/์ˆ ๋ถ€; Predicate)์„ ๋ฆฌํ„ดํ•˜๋Š” ํ•จ์ˆ˜๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ’ก ์ˆ ๋ถ€(Predicate; ่ฐ“่ฏ)๋ž€? ์ฃผ์–ด์˜ ์ƒํƒœ, ์„ฑ์งˆ ๋”ฐ์œ„๋ฅผ ์„œ์ˆ ํ•˜๋Š” ๋ง(์ฐธ๊ณ )value is Type ํ˜•ํƒœ๋Š” ์ˆ ๋ถ€๋ณด๋‹จ ๋ช…์ œ(๊ณ ๋ž˜๋Š” ํฌ์œ ๋ฅ˜๋‹ค ์ฒ˜๋Ÿผ ์ฐธ/๊ฑฐ์ง“์„ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋Š” ๋‚ด์šฉ)๊ฐ€ ๋” ์–ด์šธ๋ฆฌ๋Š” ๋‹จ์–ด์ธ ๊ฒƒ ๊ฐ™๋‹ค.

 

์•„๋ž˜ logPersonAge ํ•จ์ˆ˜๋Š” Person, Product ๋‘๊ฐ€์ง€ ํƒ€์ž…์˜ ์ธ์ž๋ฅผ ๋ฐ›๋Š”๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ์–ด๋–ค ํƒ€์ž…์˜ ๊ฐ’์„ ์ธ์ž๋กœ ๋ฐ›์„์ง€ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ํƒ€์ž…๊ฐ€๋“œ ์—†์ด value.age ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋ฉด ts(2339) ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

interface Person {
  name: string;
  age: number;
}

interface Product {
  name: string;
  price: number;
}

// ํƒ€์ž… ๊ฐ€๋“œ ํ•จ์ˆ˜๋Š” ์ฃผ๋กœ isTypeName ํ˜•ํƒœ์˜ ํ•จ์ˆ˜๋ช…์„ ์‚ฌ์šฉํ•œ๋‹ค
// is ํ‚ค์›Œ๋“œ๋Š” parameterName is Type ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•œ๋‹ค
function isPerson(value: Person | Product): value is Person {
  return (value as Person).age !== undefined; // true | false
}

function logPersonAge(value: Person | Product) {
  console.log(value.age); // Error! Property 'age' does not exist on type 'Product'
  if (isPerson(value)) console.log(value.age);
  else console.log('value does not have age properties');
}

logPersonAge({ name: 'smith', age: 30 }); // 30
logPersonAge({ name: 'iPhone', price: 600 }); // value does not have age properties

 

๐Ÿ’ก isPerson ํ•จ์ˆ˜ ๋ฆฌํ„ด ํƒ€์ž…์— ๋ช…์‹œํ•œ value is Person ๊ตฌ๋ฌธ์ด ํƒ€์ž… ๋ช…์ œ(์ˆ ๋ถ€)๋‹ค.

 

์ด์ฒ˜๋Ÿผ is ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ boolean ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ type predicates(ํƒ€์ž… ๋ช…์ œ)๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. ์ฆ‰, ๋ฐ˜ํ™˜ ํƒ€์ž…์ด ํƒ€์ž… ๋ช…์ œ์ธ ํ•จ์ˆ˜๋Š” ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ณ  ๋‹จ์ˆœํžˆ boolean ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋ฐ˜ํ™˜ํ•œ boolean ๊ฐ’์ด ์ฐธ์ด๋ผ๋ฉด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ ์Šค์ฝ”ํ”„๋‚ด์—์„œ ํ•ด๋‹น ๋ช…์ œ(value is Type)์˜ ํƒ€์ž… ์ •๋ณด๊ฐ€ ์ ์šฉ๋œ๋‹ค.

 

์•„๋ž˜๋Š” ์ž…๋ ฅ๋ฐ›์€ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ number ํƒ€์ž…์ธ์ง€ ํ™•์ธํ•˜๋Š” isNumber ํƒ€์ž…๊ฐ€๋“œ ์˜ˆ์‹œ. Array.at() ๋ฉ”์„œ๋“œ๋Š” ์ธ์ž์— ๋ช…์‹œํ•œ ์ธ๋ฑ์Šค์˜ ์š”์†Œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, ์š”์†Œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์š”์†Œํƒ€์ž… | undefined ์œ ๋‹ˆ์˜จ ํƒ€์ž…์„ ๊ฐ–๋Š”๋‹ค. ์š”์†Œ ํƒ€์ž…์„ ํ™•์ธํ•  ๋•Œ lastEl !== undefined ๊ฐ™์€ ์กฐ๊ฑด๋ฌธ ๋Œ€์‹  isNumber ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ์ ์šฉํ•˜๋ฉด undefined ํƒ€์ž… ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์‹ค์ œ๋กœ number ํƒ€์ž…์ธ์ง€๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.

// ํƒ€์ž… ๊ฐ€๋“œ ์ •์˜
const isNumber = (value: unknown): value is number => {
  return typeof value === 'number' && !isNaN(value); // true or false ๋ฐ˜ํ™˜
};

const arr = [1, 2, 3];
const lastEl = arr.at(-1); // number | undefined

// ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ number ํƒ€์ž…์ธ์ง€ ํ™•์ธ
if (isNumber(lastEl)) {
  console.log('Last element is a number:', lastEl);
} else {
  console.log('Last element is not a number or is undefined');
}

 

asserts ํ‚ค์›Œ๋“œ


asserts, is ํ‚ค์›Œ๋“œ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด์„œ ์ž ์žฌ์ ์ธ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ(throw)์‹œํ‚ค๊ณ , void๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ํ•จ์ˆ˜(๋ฆฌํ„ด๊ฐ’์ด ์—†๋Š” ํ•จ์ˆ˜)๋Š” assertion functions๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. assertion ํ•จ์ˆ˜๊ฐ€ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๋Š”๋‹ค๋ฉด ๋ฆฌํ„ด๊ฐ’์˜ ๋ช…์ œ(value is Type)๋Š” ์ฐธ์ด ๋˜๊ณ , ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ ์Šค์ฝ”ํ”„๋‚ด์—์„œ ๋ช…์ œ์˜ ํƒ€์ž… ์ •๋ณด๊ฐ€ ์ ์šฉ๋œ๋‹ค. 

๋”๋ณด๊ธฐ

์ฝ”๋“œ ์ถœ์ฒ˜ StackOverflow โ–ผ

// predicate
function exists<T>(maybe: T): maybe is NonNullable<T> {
  return maybe != null;
}

// assertion
function assertExists<T>(maybe: T): asserts maybe is NonNullable<T> {
  if (maybe === null) throw new Error(`${maybe} doesn't exist`);
}

function example1() {
  console.log('example1 begin');
  let maybe: string | undefined;

  if (exists(maybe)) {
    maybe; // string
  } else {
    maybe; // undefined
  }

  console.log('example1 end');
}

function example2() {
  console.log('example2 begin');
  let maybe: string | undefined;

  assertExists(maybe);

  maybe; // string

  console.log('example2 end');
}

example1(); // 'example1 begin' then 'example1 end'
example2(); // only 'example2 begin', then exception is thrown: `undefined doesn't exist`
// ์˜ˆ์ œ 1
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') throw new Error('value is not string');
}

function printName(name: unknown) {
  assertIsString(name);
  console.log(name);
}

printName('smith'); // smith
printName({}); // Error: value is not string
// ์˜ˆ์ œ 2
interface User {
  name: string;
  displayName: string | null;
}

// user.displayName?.toUpperCase์— ์˜ต์…”๋„ ์ฒด์ด๋‹์„ ์‚ฌ์šฉํ•˜๊ธฐ ์‹ซ๋‹ค๋ฉด ํƒ€์ž… ๋ช…์ œ๋ฅผ ์•„๋ž˜์ฒ˜๋Ÿผ ์ˆ˜์ •
// asserts user is User & { displayName: string }
function assertDisplayName(user: User): asserts user is User {
  if (!user.displayName) throw new Error('Oops! there is no displayName');
}

function logUserDisplayName(user: User) {
  assertDisplayName(user);
  console.log(user.displayName?.toUpperCase());
}

logUserDisplayName({ name: 'smith', displayName: '์Šค๋ฏธ์Šค ์š”์›' }); // ์Šค๋ฏธ์Šค ์š”์›
logUserDisplayName({ name: 'smith', displayName: null }); // Error: oops! there is no displayName

 

๋ ˆํผ๋Ÿฐ์Šค


 


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

 

๋ฐ˜์‘ํ˜•