2024-05-22.md
π‘DIL: μ΄νν°λΈ νμ μ€ν¬λ¦½νΈ
μ€ν°λ: μκ° CS, https://github.com/monthly-cs/2024-05-effective-typescript
μμ±μΌ: 2024-05-22
μμ±μ: dusunax
μμ΄ν 20: λ€λ₯Έ νμ μλ λ€λ₯Έ λ³μ μ¬μ©νκΈ° Use Different Variables for Different Types
- νμ μ€ν¬λ¦½νΈμμ "λ³μμ κ°μ λ°λ μ μμ§λ§ κ·Έ νμ μ λ³΄ν΅ λ°λμ§ μλλ€"
- νμ
μ λ°κΎΈλ λ²
- type narrowing: νμ μ "μ’νλ€"
λ€λ₯Έ νμ μλ λ³λμ λ³μλ₯Ό μ¬μ©νλ κ² λ°λμ§
let productId: string | number = "12-34-56";
fetchProduct(productId);
// νμ
μ μ’νμ λ€λ₯Έ νμ
μ μ¬μ©ν μ μμ΅λλ€.
productId = 123456; // OK
fetchProductBySerialNumber(productId); // OK
// λ€λ₯Έ λ³μλ₯Ό μ¬μ©νλ κ²μ΄ μ’μ΅λλ€
const productId = "12-34-56";
fetchProduct(productId);
const serial = 123456; // OK
fetchProductBySerialNumber(serial); // OK
// μ€μ½ν
const productId = "12-34-56";
fetchProduct(productId);
{
const productId = 123456; // OK
fetchProductBySerialNumber(productId); // OK
}
- μ₯μ
- μλ‘ κ΄λ ¨μ΄ μλ λ κ°μ κ°μ λΆλ¦¬
- λ³μλͺ μ λ ꡬ체μ μΌλ‘ μ§μ μ μμ
- νμ μΆλ‘ μ ν₯μμν€λ©°, λΆνμν νμ ꡬ문x
- νμ μ΄ κ°κ²°ν΄μ§ (μ λμ¨μ μ μΈ μ μλ€)
- let λμ constλ‘ μ μΈνμ¬, κ°κ²°νκ³ νμ μ μΆλ‘ νκΈ° μ¬μ΄ μ½λ μμ±
- κ²°λ‘
- νμ μ΄ λ°λλ λ³μλ₯Ό νΌνμ
- λͺ©μ μ΄ λ€λ₯Έ κ³³μλ λ³λμ λ³μλͺ μ μ¬μ©νμ
shadowed λ³μ
let x = 10;
function example() {
// μ§μ μ€μ½ν
let x = 20; // This 'x' shadows the outer 'x'
console.log(x); // 20
}
example();
console.log(x); // 10
Things to Remember
- While a variable's value can change, its type generally does not.
- λ³μμ κ°μ λ°λμ§λ§, μΌλ°μ μΌλ‘ νμ μ λ°λμ§ μλλ€
- To avoid confusion, both for human readers and for the type checker, avoid reusing variables for differently typed values.
- νΌλμ λ§κΈ° μν΄, νμ μ΄ λ€λ₯Έ κ°μ λ€λ£° λλ λ³μλ₯Ό μ¬μ¬μ©νμ§ μκΈ°
μμ΄ν 21: νμ λνκΈ° Understand How a Variable Gets Its Type
- λ°νμμ λͺ¨λ λ³μλ μ μΌν κ°μ κ°μ§λ€
- μ μ λΆμ μμ (νμ
μ€ν¬λ¦½νΈκ° μμ±λ μ½λλ₯Ό 체ν¬νλ μμ )μ λ³μλ
κ°λ₯ν κ°λ€μ μ§ν©
μΈ νμ μ κ°μ§λ€
Type Widening
-
μμλ‘ λ³μλ₯Ό μ΄κΈ°νν΄μ νμ μ λͺ μνμ§ μμΌλ©΄, νμ μ²΄μ»€κ° νμ μ κ²°μ ν΄μΌ νλ€.
- μ§μ λ λ¨μΌ κ°λ€μ κ°μ§κ³ , ν λΉ κ°λ₯ν κ°λ€μ μ§ν©μ μ μΆνλ€
- μ λ³΄κ° μΆ©λΆνμ§ μμΌλ©΄, μ΄λ€ νμ μΌλ‘ μΆλ‘ λμ΄μΌνλ μ§ μ μ μλ€ (μμ±μμ μλλ₯Ό μΆμΈ‘νλ€)
interface Vector3 { x: number; y: number; z: number; } function getComponent(vector: Vector3, axis: "x" | "y" | "z") { return vector[axis]; } let x = "x"; // constλ‘ μ μΈν μ "x" μ λ νμ let vec = { x: 10, y: 20, z: 30 }; getComponent(vec, x); // ~ Argument of type 'string' is not assignable // to parameter of type '"x" | "y" | "z"'
- μ§μ λ λ¨μΌ κ°λ€μ κ°μ§κ³ , ν λΉ κ°λ₯ν κ°λ€μ μ§ν©μ μ μΆνλ€
Type wideningμ μ μ΄ν μ μλ λ°©λ²
- TypeScriptμμ νμ μ λ μ’μ λ²μλ‘ μΆλ‘ νλλ‘ μ μ΄νλ λ°©λ²μλ μ¬λ¬ κ°μ§κ° μμ
- λ³μλ μμμ νμ μ λͺ μμ μΌλ‘ μ§μ νκ±°λ, νμ 체컀μ μΆκ°μ μΈ μ 보λ₯Ό μ 곡νμ¬ κ°λ₯ν ν μ’μ νμ μΌλ‘ μΆλ‘ νκ² ν¨
1. constλ‘ λ³μλ₯Ό μ μΈνλ©΄ λ μ’μ νμ μ΄ λλ€
- constλ₯Ό μ¬μ©νλ©΄ λ³μλ μ¬ν λΉμ΄ λΆκ°λ₯ν΄μ§λ―λ‘ λ μ’μ νμ μΌλ‘ μΆλ‘
- κ·Έλ¬λ κ°μ²΄μ λ°°μ΄μ κ²½μ°, λ΄λΆ μμλ€μ μ¬μ ν λμ νμ μΌλ‘ μΆλ‘ λ μ μμ
- λ€λ₯Έ μμ± μΆκ°ν μ μμ΄ κ°μ²΄λ₯Ό ν λ²μ λ§λ€μ΄μΌ ν¨(μμ΄ν 23)
2. νμ 체컀μ μΆκ°μ μΈ λ¬Έλ§₯μ μ 곡 (ν¨μμ 맀κ°λ³μλ‘ κ° μ λ¬ λ±)
function processPoint(point: { x: 10; y: 20 }) {
// processPoint λ΄λΆμμλ pointμ νμ
μ΄ { x: 10, y: 20 }λ‘ μΆλ‘ λ©λλ€.
}
const p = { x: 10, y: 20 };
processPoint(p);
3. const λ¨μΈλ¬Έμ μ¬μ© (as const)
- κ° λ€μ as constλ₯Ό μμ±νλ©΄, νμ μ€ν¬λ¦½νΈλ μ΅λν μ’μ νμ μΌλ‘ μΆλ‘ ν©λλ€.
const point = { x: 10, y: 20 } as const; // Type is { readonly x: 10, readonly y: 20 }
4.satisfies ν€μλλ₯Ό μ¬μ©
- satisfies ν€μλλ capitalsBad κ°μ²΄κ° `Record<string, Point>`` νμ μ λ§μ‘±νλμ§ νμΈ
- μ΄ κ²½μ°, κ° μμ±μ κ°μ΄ Point νμ (μ¦, [number, number])μ μΆ©μ‘±νμ§ μμΌλ©΄ μ€λ₯ λ°μ
- νΉμ§
- satisfies ν€μλλ νμ νμΈμ μννμ§λ§, λ³μμ μλ νμ μ λ³κ²½νμ§ μμ
- asμ λ¬λ¦¬, satisfiesλ νμ λ¨μΈμ νμ§ μμΌλ―λ‘ νμ μμ μ±μ μ μ§
type Point = [number, number];
const capitalsBad = {
ny: [-73.7562, 42.6526, 148],
// ~~ Type '[number, number, number]' is not assignable to type 'Point'.
ca: [-121.4944, 38.5816, 26],
// ~~ Type '[number, number, number]' is not assignable to type 'Point'.
} satisfies Record<string, Point>;
Things to Remember
- Understand how TypeScript infers a type from a literal by widening it.
- νμ μ€ν¬λ¦½νΈκ° 리ν°λ΄μμ μ΄λ»κ² νμ μ λνλ μ§ μ΄ν΄νκΈ°
- Familiarize yourself with the ways you can affect this behavior: const, type annotations, context, helper functions, as const, and satisfies.
- λμμ μν₯μ μ€ μ μλ λ°©λ²λ€:
μμ΄ν 22: νμ μ’νκΈ° Understand Type Narrowing
null 체ν¬νκΈ°
const elem = document.getElementById("what-time-is-it");
// ^? const elem: HTMLElement | null
if (!elem) throw new Error("Unable to find #what-time-is-it"); // nullμ 체ν¬ν¨
elem.innerHTML = "Party Time".blink();
// ^? const elem: HTMLElement
Type narrowing νμ μ’νκΈ° μμ
// [λΆκΈ°λ¬Έ] κ°μ νμ
μ instacneofλ₯Ό μ¬μ©νμ¬ μ’ν
function contains(text: string, search: string | RegExp) {
if (search instanceof RegExp) {
return !!search.exec(text);
// ^? (parameter) search: RegExp
}
return text.includes(search);
// ^? (parameter) search: string
}
// [μμ± μ²΄ν¬] μμ±μ μ‘΄μ¬ μ¬λΆλ‘ interface νμ
μ’νκΈ°
interface Apple {
isGoodForBaking: boolean;
}
interface Orange {
numSlices: number;
}
function pickFruit(fruit: Apple | Orange) {
if ("isGoodForBaking" in fruit) {
fruit;
// ^? (parameter) fruit: Apple
} else {
fruit;
// ^? (parameter) fruit: Orange
}
fruit;
// ^? (parameter) fruit: Apple | Orange
}
// [λ΄μ₯ ν¨μ]
function contains(text: string, terms: string | string[]) {
const termList = Array.isArray(terms) ? terms : [terms]; // λ΄μ₯ λ©μλλ‘ νμ
μ’νκΈ°
// ^? const termList: string[] μ΄μ string λ°°μ΄
// ...
}
// [tagged union] νκ·Έλ μ λμ¨, λλ ꡬλ³λ μ λμ¨ discriminated union
interface UploadEvent {
type: "upload"; // μ λμ¨!
filename: string;
contents: string;
}
interface DownloadEvent {
type: "download"; // μ λμ¨!
filename: string;
}
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent) {
switch (e.type) {
case "download": // νμ©
console.log("Download", e.filename);
// ^? (parameter) e: DownloadEvent
break;
case "upload":
console.log("Upload", e.filename, e.contents.length, "bytes");
// ^? (parameter) e: UploadEvent
break;
}
}
// μ¬μ©μ μ μ νμ
κ°λ
function isInputElement(el: Element): el is HTMLInputElement {
return "value" in el;
}
function getElementContent(el: HTMLElement) {
if (isInputElement(el)) {
// μΈν μ리먼νΈμ΄λ?
return el.value;
// ^? (parameter) el: HTMLInputElement
}
return el.textContent;
// ^? (parameter) el: HTMLElement
}
// 컀μ€ν
νμ
κ°λ μμ
function isDefined<T>(x: T | undefined): x is T {
return x !== undefined;
}
const jackson5 = ["Jackie", "Tito", "Jermaine", "Marlon", "Micheal"];
const members = ["Janet", "Micheal"]
.map((who) => jackson5.find((n) => n === who))
.filter(isDefined); // filter(x => x !== undefined)μ λ°νλλ νμ
μ μν₯μ μ£Όμ§ μμ
κΌΌκΌΌν μ΄νΌκΈ°
-
nullμ 'object'μ΄λ€.
const elem = document.getElementById("what-time-is-it"); // ^? const elem: HTMLElement | null if (typeof elem === "object") { elem; // ^? const elem: HTMLElement | null π€·ββοΈ }
-
""μ 0μ κ°μ λ³ν μ falseλ€.
function maybeLogX(x?: number | string | null) { if (!x) { console.log(x); // ^? (parameter) x: string | number | null | undefined π€·ββοΈ } }
Things to Remember
- Understand how TypeScript narrows types based on conditionals and other types of control flow.
- λΆκΈ°λ¬Έ μΈμλ λ€λ₯Έ μ’ λ₯μ μ μ΄ νλ¦μ μ΄ν΄λ³΄λ©°, νμ μ€ν¬λ¦½νΈκ° νμ μ μ’νλ κ³Όμ μ μ΄ν΄νμ
- Use tagged/discriminated unions and user-defined type guards to help the process of narrowing.
- νμ
μ’νκΈ°λ₯Ό λνκΈ° μν΄
νκ·Έλ μ λμ¨
κ³Ό,μ¬μ©μ μ μ νμ κ°λ
λ₯Ό μ¬μ©ν μ μλ€.
- νμ
μ’νκΈ°λ₯Ό λνκΈ° μν΄
- Think about whether code can be refactored to let TypeScript follow along more easily.
- νμ μ€ν¬λ¦½νΈκ° μκΈ° μ¬μ΄ μ½λλ‘ λ¦¬ν©ν λ§νλ κ²μ κ³ λ €
μμ΄ν 23: νκΊΌλ²μ κ°μ²΄ μμ±νκΈ° Create Objects All at Once
- λ³μμ κ°μ λ³κ²½o, νμ
μ€ν¬λ¦½νΈμ νμ
μ μΌλ°μ μΌλ‘ λ³κ²½x
- κ°μ²΄λ₯Ό μμ±ν λλ μμ±μ νλμ© μΆκ°νκΈ° 보λ€λ, μ¬λ¬ μμ±μ ν¬ν¨νμ¬ νκΊΌλ²μ μμ±ν΄μΌ νμ μΆλ‘ μ μ 리ν©λλ€
const pt = {}; // λ³μμ νμ
μ΄ {} κΈ°μ€μΌλ‘ μΆλ‘ λ©λλ€.
// ^? const pt: {}
pt.x = 3;
// ~ Property 'x' does not exist on type '{}'
// μ‘΄μ¬νμ§ μλ μμ±μ μΆκ°ν μ μμ΅λλ€
pt.y = 4;
// ~ Property 'y' does not exist on type '{}'
// interface
interface Point {
x: number;
y: number;
}
const pt: Point = {}; // μμ±μ΄ μλ€!
// ~~ Type '{}' is missing the following properties from type 'Point': x, y
pt.x = 3;
pt.y = 4;
// ν΄κ²°A: ν λ²μ κ°μ²΄ μμ±νκΈ°
const pt: Point = {
x: 3,
y: 4, // ν λ²μ μ μνκΈ°!
};
// ν΄κ²°B: λ¨μΈ
const pt = {} as Point;
// ^? const pt: Point
pt.x = 3;
pt.y = 4; // OK
κ°μ²΄ μ κ° μ°μ°μ (spread) μ¬μ©νκΈ°
- νλ λ¨μμ κ°μ²΄ μμ± κ°λ₯
- κ°μ²΄μ μμ±μ μΆκ°νκ³ , νμ μ€ν¬λ¦½νΈκ° μλ‘μ΄ νμ μ μΆλ‘ ν μ μκ² νλ λ°©λ²
const pt0 = {};
const pt1 = { ...pt0, x: 3 };
const pt: Point = { ...pt1, y: 4 }; // OK
- μ‘°κ±΄λΆ μμ±μ μΆκ°νκΈ° μμ
- {} λλ nullλ‘ κ°μ²΄ μ κ°λ₯Ό μ¬μ©νλ€
declare let hasMiddle: boolean;
const firstLast = { first: "Harry", last: "Truman" };
const president = { ...firstLast, ...(hasMiddle ? { middle: "S" } : {}) };
// ^? const president: {
// middle?: string; // μ νμ μμ±μΌλ‘ μΆλ‘ λ¨!
// first: string;
// last: string;
// }
// or: const president = {...firstLast, ...(hasMiddle && {middle: 'S'})};
- μ κ° μ°μ°μλ‘ μ¬λ¬ μμ±μ μΆκ°νκΈ°
function addOptional<T extends object, U extends object>(
a: T,
b: U | null
): T & Partial<U> {
return { ...a, ...b };
}
Things to Remember
- Prefer to build objects all at once rather than piecemeal.
- μμ±μ μ κ°κ° μΆκ°νμ§ λ§κ³ , νλ²μ κ°μ²΄λ‘ λ§λ€κΈ°
- Use multiple objects and object spread syntax ({...a, ...b}) to add properties in a type-safe way.
- μ¬λ¬ μ€λΈμ νΈλ₯Ό μ€νλ λ λ¬Έλ²μ μ¬μ©ν΄μ μμ ν νμ μΌλ‘ μΆκ°ν μ μλ€
- Know how to conditionally add properties to an object.
- κ°μ²΄μ 쑰건λΆλ‘ μμ± μΆκ°νλ λ² μμλκΈ° (μ΅μ λ μμ±)