2024-05-23.md

๐Ÿก

DIL: ์ดํŽ™ํ‹ฐ๋ธŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ

์Šคํ„ฐ๋””: ์›”๊ฐ„ CS, https://github.com/monthly-cs/2024-05-effective-typescript
์ž‘์„ฑ์ผ: 2024-05-23
์ž‘์„ฑ์ž: dusunax


์•„์ดํ…œ 24: ์ผ๊ด€์„ฑ ์žˆ๋Š” ๋ณ„์นญ ์‚ฌ์šฉํ•˜๊ธฐ Be Consistent in Your Use of Aliases

  • ๋ณ„์นญ์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋ฉด ์›๋ž˜ ์†์„ฑ๊ฐ’์—์„œ๋„ ๋ณ€๊ฒฝ๋œ๋‹ค
    • ์ œ์–ด ํ๋ฆ„์„ ๋ถ„์„ํ•˜๊ธฐ ์–ด๋ ต๋‹ค
const place = { name: "New York", latLng: [41.6868, -74.2692] };
const loc = place.latLng; // ๋ณ„์นญ alias

์ œ์–ด ํ๋ฆ„ ์˜ˆ์‹œ, ๋น„๊ตฌ์กฐํ™”

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  if (polygon.bbox) {
    // ๋ฐ˜๋ณต ์ฝ”๋“œ
    if (
      pt.x < polygon.bbox.x[0] ||
      pt.x > polygon.bbox.x[1] ||
      pt.y < polygon.bbox.y[0] ||
      pt.y > polygon.bbox.y[1]
    ) {
      return false;
    }
  }

  // ... more complex check
}

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  const box = polygon.bbox; // ์ž„์‹œ ๋ณ€์ˆ˜ + strictNullChecks
  // box๋ผ๋Š” ๋ณ„์นญ์„ ๋งŒ๋“ค์–ด์„œ ์ œ์–ด ํ๋ฆ„ ๋ถ„์„์„ ๋ฐฉํ•ดํ–ˆ๋‹ค

  if (polygon.bbox) {
    box.x[0]; // ์†์„ฑ ์ฒดํฌ ๋˜์ง€ ์•Š์•˜์Œ
    //~~~  'box' is possibly 'undefined'
  }

  if (box) {
    box.x[0]; // ํƒ€์ž… ์ฒด์ปค์— ๋ฌธ์ œ ์—†์ง€๋งŒ. box์™€ bbox๋ผ๋Š” ๊ฐ™์€ ๊ฐ’์ธ ๋ฐ ๋‹ค๋ฅธ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๋Š” ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค
  }

  const { bbox } = polygon; // ๊ฐ์ฒด ๋น„๊ตฌ์กฐํ™”๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.
  if (bbox) {
    const { x } = bbox; // ๋ฐฐ์—ด, ์ค‘์ฒฉ๋œ ๊ตฌ์กฐ์—์„œ๋„ ์‚ฌ์šฉํ•œ๋‹ค
    // ๋ณด๋‹ค ๊ฐ„๊ฒฐํ•œ ๋ฌธ๋ฒ•์œผ๋กœ ์ผ๊ด€๋œ ์ด๋ฆ„ ์‚ฌ์šฉํ•˜๊ธฐ
    // ์„ ํƒ์  ์†์„ฑ์ธ ๊ฒฝ์šฐ, ์†์„ฑ ์ฒดํฌ๊ฐ€ ๋” ํ•„์š”. ๊ฒฝ๊ณ„์— null๊ฐ’ ์ถ”๊ฐ€
  }
}

์ œ์–ด ํ๋ฆ„ ๋ถ„์„ Control Flow Analysis

  • ์ง€์—ญ ๋ณ€์ˆ˜์—์„œ๋Š” O. ๊ฐ์ฒด ์†์„ฑ์—์„œ๋Š” ์ฃผ์˜
  • ์ง€์—ญ ๋ณ€์ˆ˜๋กœ ๋ฝ‘์•„์„œ ์‚ฌ์šฉํ•˜๋ฉด? ๋ณ€์ˆ˜์˜ ํƒ€์ž…์€ ์œ ์ง€๋˜์ง€๋งŒ ์›๋ž˜ ๊ฐ์ฒด์˜ ๊ฐ’๊ณผ ๊ฐ™๊ฒŒ ์œ ์ง€๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ
const myObj = {
  value: 10,
};

let val = myObj.value;
val += 5;

console.log(`๊ฐ์ฒด์˜ ๊ฐ’: ${myObj.value}`); // ๊ฐ์ฒด์˜ ๊ฐ’: 10
console.log(`๊ฐ’: ${val}`); // ์ง€์—ญ ๋ณ€์ˆ˜์˜ ๊ฐ’: 15

Things to Remember

  • Aliasing can prevent TypeScript from narrowing types. If you create an alias for a variable, use it consistently.
    • ๋ณ„์นญ์€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํƒ€์ž…์„ ์ขํžˆ๋Š” ๊ฒƒ์„ ๋ฐฉํ•ดํ•˜๊ธฐ ๋•Œ๋ฌธ์—~ ์ผ๊ด€์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•จ
  • Be aware of how function calls can invalidate type refinements on properties. Trust refinements on local variables more than on properties.
    • ํ•จ์ˆ˜์˜ ํ˜ธ์ถœ์ด ๊ฐ์ฒด ์†์„ฑ์˜ ํƒ€์ž… ์ •์ œ refinements๋ฅผ ๋ฌดํšจํ™” ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ์ฃผ์˜
    • ์†์„ฑ๋ณด๋‹ค ์ง€์—ญ ๋ณ€์ˆ˜์˜ ํƒ€์ž… ์ •์ œ๋ฅผ ๋ฏฟ์œผ์„ธ์šฉ

์•„์ดํ…œ 25: ๋น„๋™๊ธฐ ์ฝ”๋“œ์—๋Š” ์ฝœ๋ฐฑ ๋Œ€์‹  async ํ•จ์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ Use async Functions Instead of Callbacks to Improve Type Flow

  • ๋น„๋™๊ธฐ ๋™์ž‘์„ ๋ชจ๋ธ๋ง ํ•˜๊ธฐ ์œ„ํ•ด ๋งˆ์ฃผํ–ˆ๋˜ ์ฝœ๋ฐฑ ์ง€์˜ฅ ๐Ÿ˜ˆ
  • ES2015์˜ Promise ๋„์ž… ๐Ÿ˜‡ (future๋ผ๊ณ  ๋ถ€๋ฅด๊ธฐ๋„ ํ•œ๋‹ค?)
    • (1)์ฝ”๋“œ ์ค‘์ฒฉ์„ ์ค„์ด๊ณ , (2)์‹คํ–‰ ์ˆœ์„œ๋ฅผ ์ฝ”๋“œ ์ˆœ์„œ์™€ ๊ฐ™๊ฒŒ ํ•จ. (3)์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ฐ€ ์‰ฝ๊ณ  (4)Promise.all๊ณผ ๊ฐ™์€ ๊ธฐ๋ฒ• ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

async function

  • await ํ‚ค์›Œ๋“œ๋Š” Promise๊ฐ€ resolve(์ฒ˜๋ฆฌ)๋  ๋•Œ๊นŒ์ง€ ํ•ด๋‹น ํ•จ์ˆ˜์˜ ์‹คํ–‰์„ ๋ฉˆ์ถค
  • async ํ•จ์ˆ˜ ๋‚ด์—์„œ await ์ค‘์ธ Promise๊ฐ€ reject(๊ฑฐ์ ˆ)๋˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค
    • try/catch

async function & typescipt

  • callback๋ณด๋‹ค promise๋ฅผ ์‚ฌ์šฉํ•  ์ด์œ 
    • ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ๋” ์‰ฝ๋‹ค
    • ํƒ€์ž…์„ ์ถ”๋ก ํ•˜๊ธฐ ๋” ์‰ฝ๋‹ค
  • Promise.all
    • awiat๊ณผ ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น
// ํ”„๋กœ๋ฏธ์Šค
async function fetchPages() {
  // ๊ฐ response์˜ ํƒ€์ž…์„ ์ถ”๋ก 
  const [response1, response2, response3] = await Promise.all([
    fetch(url1),
    fetch(url2),
    fetch(url3),
  ]);
}

// ์ฝœ๋ฐฑ ํ•จ์ˆ˜
function fetchPagesWithCallbacks() {
  let numDone = 0;
  const responses: string[] = [];
  const done = () => {
    const [response1, response2, response3] = responses;
  };
  const urls = [url1, url2, url3];
  urls.forEach((url, i) => {
    fetchURL(url, (r) => {
      responses[i] = url;
      numDone++;
      if (numDone === urls.length) done();
    });
  });
  // ์ฝ”๋“œ์™€ ํƒ€์ž… ๊ตฌ๋ฌธ์„ ๋” ๋งŽ์ด ์จ์•ผํ•œ๋‹ค
}

Promise.race

  • ํƒ€์ž„์•„์›ƒ์„ ์ถ”๊ฐ€ํ•œ ํŒจํ„ด
/** ์‹œ๊ฐ„ ๋‚ด์— HTTP ์š”์ฒญ์„ ์™„๋ฃŒํ•˜์ง€ ๋ชปํ•˜๋ฉด "timeout"์„ timeoutMs์™€ ํ•จ๊ป˜ ๋ฆฌํ„ดํ•˜๋Š” ํ•จ์ˆ˜ */
function timeout(timeoutMs: number): Promise<never> {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject("timeout"), timeoutMs);
  });
}

// (ํƒ€์ž…) Promise<Response>๋กœ ์ถ”๋ก ๋œ๋‹ค
// Promise<Reponse | never>๋Š”??
// ๊ณต์ง‘ํ•ฉ(never)์™€ ์œ ๋‹ˆ์˜จ์€ ์•„๋ฌด๋Ÿฐ ํšจ๊ณผ๊ฐ€ ์—†์œผ๋ฏ€๋กœ => Promise<Response>๋‹ค.
async function fetchWithTimeout(url: string, timeoutMs: number) {
  return Promise.race([fetch(url), timeout(timeoutMs)]); // ๋‘˜ ์ค‘ ๋ˆ„๊ฐ€ ๋จผ์ € ์™„๋ฃŒ๋  ๊ฒƒ์ธ๊ฐ€?
  // url๊ณผ ํƒ€์ž„์•„์›ƒ ๊ฐ’์„ ๋ฐ›์•„, ์ง€์ •๋œ ์‹œ๊ฐ„ ๋‚ด HTTP ์š”์ฒญ์„ ์™„๋ฃŒํ•˜์ง€ ๋ชปํ•˜๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ
  // ๋งŒ์•ฝ fetch๊ฐ€ ๋จผ์ € ์™„๋ฃŒ๋˜๋ฉด? ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
  // ๋งŒ์•ฝ timout์ด ๋จผ์ € ์™„๋ฃŒ๋˜๋ฉด? 'timeout' ์—๋Ÿฌ ๋ฐ˜ํ™˜
}

new Promise๋ณด๋‹ค async function ์‚ฌ์šฉํ•˜๊ธฐ

  • ํ”„๋กœ๋ฏธ์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ๋ณด๋‹ค, async/await๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค
    • async ํ•จ์ˆ˜๋Š” Promise๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค!
// async ํ™”์‚ดํ‘œ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ
const getNumber = async () => 42;
//    ^? const getNumber: () => Promise<number>

// ํ”„๋กœ๋ฏธ์Šค๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•œ๋‹ค๋ฉด
const getNumber = () => Promise.resolve(42);
//    ^? const getNumber: () => Promise<number>
  • ์ฆ‰์‹œ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ๊ฐ’์ž„์—๋„ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜?
    • ํ•จ์ˆ˜๋Š” ๋™๊ธฐ or ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰ / ํ˜ผ์šฉํ•˜์ง€ ์•Š๊ธฐ (๊ด€๋ฆฌํ•˜๊ธฐ ํ—ฌ์ด ๋œ๋‹ค, async () => Promise<Promise>)
    • ์ค‘๋ณต ํ”„๋กœ๋ฏธ์Šค ๋ž˜ํ•‘x, ๋น„๋™๊ธฐ ์ฝ”๋“œ์˜ ๊ฐœ๋… ์žก๊ธฐ~
const _cache: { [url: string]: string } = {};
async function fetchWithCache(url: string) {
  if (url in _cache) {
    return _cache[url];
  }
  const response = await fetch(url); // ์ผ๊ด€์ ์ธ ๋™์ž‘ ๊ฐ•์ œ
  const text = await response.text(); // await!
  _cache[url] = text;
  return text;
}

let requestStatus: "loading" | "success" | "error";
async function getUser(userId: string) {
  requestStatus = "loading";
  const profile = await fetchWithCache(`/user/${userId}`);
  requestStatus = "success";
}

Things to Remember

  • Prefer Promises to callbacks for better composability and type flow.
    • Promise๊ฐ€ callback๋ณด๋‹ค ์ฝ”๋“œ ์ž‘์„ฑ๊ณผ, ํƒ€์ž… ์ถ”๋ก  ๋ฉด์—์„œ ์œ ๋ฆฌํ•˜๋‹ค.
  • Prefer async and await to raw Promises when possible. They produce more concise, straightforward code and eliminate whole classes of errors.
    • ๊ฐ„๊ฒฐํ•˜๊ณ  ์ง๊ด€์ ์ธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์ง์ ‘ Promise๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , async/await์„ ์‚ฌ์šฉํ•˜์ž
  • If a function returns a Promise, declare it async.
    • ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋ฉด async๋กœ ์„ ์–ธํ•˜๊ธฐ