๋ฐ˜์‘ํ˜•

Object.keys ๋ฉ”์„œ๋“œ์˜ ํƒ€์ž… ์ •์˜


ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋ฉด ์•„๋ž˜ ๊ฐ™์€ ์ƒํ™ฉ์„ ์ž์ฃผ ๋งˆ์ฃผํ•œ๋‹ค. Object.keys() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐ์ฒด์˜ ํ‚ค๋ฅผ ๋ฐฐ์—ด๋กœ ์ถ”์ถœํ•œ ํ›„, ํ•ด๋‹น ํ‚ค๋ฅผ ์ด์šฉํ•ด ๊ฐ์ฒด์— ์ ‘๊ทผํ•  ๋•Œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

type Options = { host: string; port: number };

const validateOptions = (options: Options) => {
  const keys = Object.keys(options); // string[]
  keys.forEach((key) => {
    // Error! 'Options' ํ˜•์‹์—์„œ 'string' ํ˜•์‹์˜ ๋งค๊ฐœ ๋ณ€์ˆ˜๊ฐ€ ํฌํ•จ๋œ ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
    if (options[key] === null) {
      throw new Error(`Missing option ${key}`);
    }
  });
};

 

์œ„ ๋ฌธ์ œ๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ as ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํƒ€์ž… ๋‹จ์–ธํ•˜๋ฉด ์‰ฝ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋Š” ์™œ ๋ฐœ์ƒํ•˜๋Š”๊ฑธ๊นŒ?

const validateOptions = (options: Options) => {
  const keys = Object.keys(options) as (keyof Options)[];
  // ...
};

 

Object.keys ํƒ€์ž… ์ •์˜๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ ํ•ญ์ƒ string[]์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋˜์–ด ์žˆ๋‹ค.

// lib.es5.d.ts
interface ObjectConstructor {
  // ...
  keys(o: object): string[];
}

 

Object.keys() ๋ฉ”์„œ๋“œ๊ฐ€ ๊ฐ์ฒด ์ œ๋„ค๋ฆญ ํƒ€์ž… T๋ฅผ ๋ฐ›์•„ (keyof T)[]๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋ฉด ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ด๋ ‡๊ฒŒ ํƒ€์ž…์„ ์ •์˜ํ•˜์ง€ ์•Š์€ ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ? ์ด๋Š” ๊ตฌ์กฐ์  ํƒ€์ž… ์‹œ์Šคํ…œ๊ณผ ๊ด€๋ จ ์žˆ๋‹ค.

export {}; // declare global์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์™ธ๋ถ€ ๋ชจ๋“ˆ๋กœ ์ธ์‹ํ•˜๋„๋ก ๋งŒ๋“ฆ

declare global {
  interface ObjectConstructor {
    // ObjectConstructor ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™•์žฅํ•˜์—ฌ Object.keys ๋ฉ”์„œ๋“œ ํƒ€์ž… ๋ฎ์–ด์“ฐ๊ธฐ
    keys<T extends object>(o: T): (keyof T)[];
  }
}

const validateOptions = (options: Options) => {
  const keys = Object.keys(options); // (keyof Options)[]
  // ...
};

 

๐Ÿ’ก ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ปดํŒŒ์ผ๋Ÿฌ๋Š” export ํ˜น์€ import ๊ตฌ๋ฌธ์ด ์—†๋Š” ํŒŒ์ผ์„ ์ผ๋ฐ˜ ์Šคํฌ๋ฆฝํŠธ๋กœ ์ทจ๊ธ‰ํ•œ๋‹ค. declare global์„ ์ด์šฉํ•ด ์ „์—ญ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋Š” ์„ ์–ธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด export {} ๋“ฑ์„ ์‚ฌ์šฉํ•ด ์™ธ๋ถ€ ๋ชจ๋“ˆ๋กœ ์ธ์‹ํ•˜๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.

 

๊ตฌ์กฐ์  ํƒ€์ดํ•‘ Structural Typing


๊ตฌ์กฐ์  ํƒ€์ดํ•‘(Structural Typing)์€ ์ฝ”๋“œ ๊ตฌ์กฐ์˜ ๊ด€์ ์—์„œ ํƒ€์ž… ํ˜ธํ™˜์„ฑ์„ ํŒ๋‹จํ•˜๋Š” ๋ฐฉ์‹์„ ์˜๋ฏธํ•œ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐ์ฒด ์†์„ฑ ์ˆ˜, ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆ˜ ๋“ฑ์ด ๋น„๊ต ๋Œ€์ƒ๋ณด๋‹ค ๋งŽ์„ ๊ฒฝ์šฐ, ๊ตฌ์กฐ์ ์œผ๋กœ ๋” ํฐ ํƒ€์ž…์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

์•„๋ž˜ ์˜ˆ์‹œ์—์„œ User ํƒ€์ž…์€ name, age ์†์„ฑ์„ ๊ฐ–๋Š”๋‹ค. ํ•˜์ง€๋งŒ city ๋ผ๋Š” ์ถ”๊ฐ€ ์†์„ฑ์„ ๊ฐ–๋Š” userThree ๊ฐ์ฒด๋ฅผ ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ๋„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

type User = { name: string; age: number };

const saveUser = (user: User) => {};

const userOne = { name: 'One', age: 25 };
saveUser(userOne); // Ok

const userTwo = { name: 'Two' };
saveUser(userTwo); // ts(2345) Error! 'age' ์†์„ฑ์ด '{ name: string; }' ํ˜•์‹์— ์—†์ง€๋งŒ 'User' ํ˜•์‹์—์„œ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค

const userThree = { name: 'Three', age: 35, city: 'Seoul' };
saveUser(userThree); // OK

 

๊ตฌ์กฐ์  ํƒ€์ž… ์‹œ์Šคํ…œ์—์„  ๊ธฐ๋ณธ์ ์œผ๋กœ ํƒ€์ž… A๊ฐ€ B์˜ ์Šˆํผ์…‹์ธ ๊ฒฝ์šฐ(A๋Š” B์˜ ๋ชจ๋“  ์†์„ฑ์„ ํฌํ•จํ•˜๊ณ  ์ถ”๊ฐ€ ์†์„ฑ๋„ ๊ฐ€์งˆ ๋•Œ) A๋ฅผ B์— ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋‹ค. (๊ณต๋ณ€์„ฑ, ๋ฐ˜๊ณต๋ณ€์„ฑ๊ณผ๋Š” ๋ณ„๋„์˜ ๊ฐœ๋…)

  • ์Šˆํผ์…‹ A๋Š” ์„œ๋ธŒ์…‹ B์— ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋‹ค
  • ์„œ๋ธŒ์…‹ B๋Š” ์Šˆํผ์…‹ A์— ํ• ๋‹นํ•  ์ˆ˜ ์—†๋‹ค

 

type Super = { name: string; age: number };
type Sub = { name: string };

const aSuper: Super = { name: 'super', age: 25 };
const aSub: Sub = { name: 'sub' };

const a: Sub = aSuper; // OK
const b: Super = aSub; // ts(2741) Error! 'age' ์†์„ฑ์ด 'Sub' ํ˜•์‹์— ์—†์ง€๋งŒ 'Super' ํ˜•์‹์—์„œ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค

 

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

 

์ฆ‰, ํ•ด๋‹น ๊ฐ์ฒด๊ฐ€ ์ •ํ™•ํžˆ ํƒ€์ž… T์˜ ํ”„๋กœํผํ‹ฐ๋งŒ ๊ฐ–๋Š”์ง€, ์•„๋‹ˆ๋ฉด ์ถ”๊ฐ€์ ์ธ ํ”„๋กœํผํ‹ฐ๋„ ๊ฐ–๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค๋Š” ๋ง์ด๋‹ค. ๋•Œ๋ฌธ์— Object.keys ๋ฉ”์„œ๋“œ๋Š” ๊ฐ ํ”„๋กœํผํ‹ฐ์˜ ์ •ํ™•ํ•œ ํƒ€์ž… ์ •๋ณด๊ฐ€ ์•„๋‹Œ, ๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ ํ‚ค๋ฅผ ๋ชจ๋‘ string์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

Object.keys ์‚ฌ์šฉ์‹œ ์ฃผ์˜ํ•  ์ 


๐Ÿ’ก a ||= b ๋ฌธ๋ฒ•์€ ES2021์— ๋„์ž…๋œ ์‹ ๊ทœ ๊ธฐ๋Šฅ์œผ๋กœ a๊ฐ€ falsy์ด๋ฉด b๋ฅผ a์— ํ• ๋‹นํ•œ๋‹ค — ์ฐธ๊ณ  ํฌ์ŠคํŒ…

 

์•„๋ž˜ ์˜ˆ์‹œ์—์„œ validateUser ํ•จ์ˆ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” User ๊ฐ์ฒด ํƒ€์ž…์„ ๋ฐ›๋„๋ก ์ž‘์„ฑํ–ˆ์ง€๋งŒ, ๊ตฌ์กฐ์  ํƒ€์ดํ•‘ ํŠน์„ฑ์œผ๋กœ ์ธํ•ด email ์ถ”๊ฐ€ ์†์„ฑ์„ ๊ฐ–๋Š” ๊ฐ์ฒด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด(๋Ÿฐํƒ€์ž„) validators ๊ฐ์ฒด์— email ๋ฉ”์„œ๋“œ๊ฐ€ ์—†์œผ๋ฏ€๋กœ “validate is not a function” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

๋‹คํ–‰ํžˆ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š๋”๋ผ๋„(์ปดํŒŒ์ผ ์‹œ์ ) Object.keys() ๋ฉ”์„œ๋“œ๋Š” ํ•ญ์ƒ string[] ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ validators[key] ๋ถ€๋ถ„์—์„œ ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” key๊ฐ€ string ํƒ€์ž…์ธ ๊ฒƒ์€ ์•Œ๊ณ  ์žˆ์ง€๋งŒ ๊ฐ ๋ฌธ์ž์—ด์ด validators ๊ฐ์ฒด์˜ ํ‚ค์ธ์ง€๋Š” ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์—†์–ด์„œ ๊ทธ๋Ÿฐ ๊ฒƒ.

 

๋งŒ์•ฝ Object.keys() ๋ฉ”์„œ๋“œ๊ฐ€ string[]์ด ์•„๋‹Œ validators ๊ฐ์ฒด์˜ ํ‚ค ์ด๋ฆ„์„ ์š”์†Œ๋กœ ๊ฐ–๋Š” ๋ฐฐ์—ด ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ–ˆ๋‹ค๋ฉด, ์•„๋ž˜์ฒ˜๋Ÿผ ์ถ”๊ฐ€์ ์ธ ์†์„ฑ์„ ๊ฐ€์ง„ ๊ฐ์ฒด๋ฅผ ๋„˜๊ฒผ์„ ๋•Œ ์ปดํŒŒ์ผ ์‹œ์ ์—๋Š” ์—๋Ÿฌ๋ฅผ ์บ์น˜ํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋Ÿฐํƒ€์ž„ ์‹œ์ ์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๊ฒƒ์ด๋‹ค.

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

const validators = {
  name: (name: string) => { ... },
  password: (password: string) => { ... },
};

const validateUser = (user: User) => {
  let error = '';
  const keys = Object.keys(user); // string[]

  for (const key of keys) {
    const validate = validators[key]; // ์ปดํŒŒ์ผ Error! Expression of type 'string' can't be used to index type { ... }
    error ||= validate(user[key]); // ๋Ÿฐํƒ€์ž„ Error! validate is not a function
  }

  return error;
};

const user = { name: 'John', password: '1q2w3e4r', email: 'john@gmail.com' };
validateUser(user); // email ์ถ”๊ฐ€ ํ”„๋กœํผํ‹ฐ๋กœ ์ธํ•œ ์—๋Ÿฌ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ

 

์œ„ ์˜ˆ์‹œ์˜ ํƒ€์ž… ์—๋Ÿฌ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ํƒ€์ž…๊ฐ€๋“œ๋ฅผ ์‚ฌ์šฉํ•ด key๊ฐ€ validators ๊ฐ์ฒด์˜ ํ‚ค ์ค‘ ํ•˜๋‚˜์ž„์„ ์•Œ๋ ค์ค˜์•ผ ํ•œ๋‹ค. ํƒ€์ž…๊ฐ€๋“œ๋Š” ํŠน์ • ์Šค์ฝ”ํ”„์— ์žˆ๋Š” ํƒ€์ž…์„ ๋Ÿฐํƒ€์ž„ ๋•Œ ์ฒดํฌํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ is, typeof, instanceof ๋“ฑ์˜ ํ‚ค์›Œ๋“œ๋ฅผ ํ™œ์šฉํ•œ๋‹ค. ์•„๋ž˜๋Š” is ํ‚ค์›Œ๋“œ๋ฅผ ์ด์šฉํ•ด ํƒ€์ž…์„ ๊ฒ€์‚ฌํ•˜๋Š” ์˜ˆ์‹œ.

// ...

const isKeyOfValidators = (key: string): key is keyof typeof validators => {
  return key in validators;
};

const validateUser = (user: User) => {
  let error = '';

  const keys = Object.keys(user); // string[]
  for (const key of keys) {
    if (isKeyOfValidators(key)) {
      const validate = validators[key];
      error ||= validate(user[key]);
    }
  }
  return error;
};

 

๊ตฌ์กฐ์  ํƒ€์ดํ•‘ ํ™œ์šฉํ•˜๊ธฐ โญ


๊ตฌ์กฐ์  ํƒ€์ดํ•‘์€ ๋งŽ์€ ์œ ์—ฐํ•จ์„ ์ œ๊ณตํ•œ๋‹ค. ์•„๋ž˜ getKeyboardShortcut ํ•จ์ˆ˜๋Š” KeyboardEvent ๊ฐ์ฒด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„ ๋‹จ์ถ•ํ‚ค ์กฐํ•ฉ์„ ํ™•์ธํ•˜๊ณ  ๋Œ€์‘ํ•˜๋Š” ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

function getKeyboardShortcut(e: KeyboardEvent) {
  // ์ฐธ๊ณ ๋กœ ๋ฉ”ํƒ€ ํ‚ค๋Š” ์œˆ๋„์šฐ์—์„œ windows ํ‚ค, macOS์—์„  command ํ‚ค
  if (e.key === 's' && e.metaKey) return 'save';
  if (e.key === 'o' && e.metaKey) return 'open';

  return null;
}

 

์œ„ ํ•จ์ˆ˜๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์ฒ˜๋Ÿผ ๋ช‡ ๊ฐ€์ง€ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ํ•จ์ˆ˜ ์ธ์ž๋กœ ๋„˜๊ธด ๊ฐ์ฒด๋Š” KeyboardEvent ํƒ€์ž…์— ์žˆ๋Š” altKey, code ๋“ฑ 37๊ฐœ ์†์„ฑ์ด ์—†๋‹ค๋Š” ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

expect(getKeyboardShortcut({ key: 's', metaKey: true })).toEqual('save');
expect(getKeyboardShortcut({ key: 'o', metaKey: true })).toEqual('open');
expect(getKeyboardShortcut({ key: 's', metaKey: false })).toEqual(null);

 

 

์ž„์‹œ ๋ฐฉํŽธ์œผ๋กœ as ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ํƒ€์ž… ๋‹จ์–ธํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ž ์žฌ์ ์œผ๋กœ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค๋ฅธ ํƒ€์ž… ์—๋Ÿฌ๋ฅผ ๋†“์น  ์ˆ˜ ์žˆ๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค.

getKeyboardShortcut({ key: 's', metaKey: true } as KeyboardEvent);

 

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

interface KeyboardShortcutEvent {
  key: string;
  metaKey: boolean;
}

function getKeyboardShortcut(e: KeyboardShortcutEvent) {
  // ...
}

 

์œ„์ฒ˜๋Ÿผ ์ž‘์„ฑํ•˜๋ฉด getKeyboardShortcut ํ•จ์ˆ˜๊ฐ€ KeyboardEvent ํƒ€์ž…์— ๋œ ์ข…์†์ ์ด๊ฒŒ ๋˜์–ด ๋” ๋‹ค์–‘ํ•œ ์ปจํ…์ŠคํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์žฅ์ ๋„ ์žˆ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ๋ชจ๋“  KeyboardEvent ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง€๋Š” ์ด๋ฒคํŠธ ๊ฐ์ฒด๋ฅผ ๋„˜๊ธธ ์ˆ˜๋„ ์žˆ๋‹ค. KeyboardEvent ํƒ€์ž…์ด KeyboardShortcutEvent ํƒ€์ž…์˜ ์Šˆํผ์…‹(๋” ํฐ ํƒ€์ž…)์ด๊ธฐ ๋•Œ๋ฌธ์— 37๊ฐœ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์•„๋„ ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ ๊ฒƒ์ด๋‹ค.

window.addEventListener('keydown', (e: KeyboardEvent) => {
  const shortcut = getKeyboardShortcut(e); // OK!
  if (shortcut) execShortcut(shortcut);
});

 

๋ ˆํผ๋Ÿฐ์Šค


 

Why doesn't TypeScript properly type Object.keys?

A look at TypeScript's structural type system, and we how we can effectively use it to our benefit.

alexharri.com

 


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

 

๋ฐ˜์‘ํ˜•