2024-05-17.md
π‘DIL: μ΄νν°λΈ νμ μ€ν¬λ¦½νΈ
μ€ν°λ: μκ° CS, https://github.com/monthly-cs/2024-05-effective-typescript
μμ±μΌ: 2024-05-17
μμ±μ: dusunax
μμ΄ν 8 μ΄μ΄μ
μ€κ° μ μ₯~!~!
17μΌ λͺ©ν: ~12μ₯κΉμ§
μ΄ νμ΅μκ°: νμ¬ 1μκ° 50λΆ
μμ± μ κ·Όμ [], property accessor
- obj['field']μ obj.fieldλ κ°μ΄ λμΌνλλΌλ νμ μ λ€λ₯Ό μ μλ€.
const jane: Person = { first: "Jane", last: "Jacobs" };
const first: Person["first"] = jane["first"]; // Or jane.first
// βββββ βββββββββββββ Values. "Jane"
// ββββββ βββββββ Types, νμ
λ§₯λ½μμ μ°μκΈ° λλ¬Έμ νμ
-
νμ μ μμ±μ μ»μ λλ,
[]
λ₯Ό μ¬μ©νλ€.-
type.fieldλ‘ μ κ·Όν μ μμ
Cannot access 'Person.first' because 'Person' is a type, but not a namespace. Did you mean to retrieve the type of the property 'first' in 'Person' with 'Person["first"]'?
-
Person ν΄λμ€λ₯Ό νμ (type)μΌλ‘ μ¬μ©ν κ²μ΄κΈ° λλ¬Έμ, λ€μμ€νμ΄μ€μ²λΌ μμ±μ μ§μ μ κ·Όν μ μλ€.
- ν΄λμ€: νΉμ κ°μ²΄λ₯Ό μμ±νκΈ° μν blueprint
- λ€μμ€νμ΄μ€: μ½λλ₯Ό λ Όλ¦¬μ μΌλ‘ κ·Έλ£Ήννκ³ , μ μ μ€μ½νμ μ€μΌμ λ°©μ§ (λ€μμ€νμ΄μ€λ₯Ό ν΅ν΄ ν¨μ, ν΄λμ€, λ³μ λ±μ μ κ·Ό)
-
-
μΈλ±μ€μλ union, κΈ°λ³Έν (js νμ )μ ν¬ν¨ν΄ μ΄λ€ νμ μ΄λ μ¬μ©ν μ μλ€.
type PersonEl = Person["first" | "last"]; // union!
// ^? type PersonEl = string
type Tuple = [string, number, Date]; // κΈ°λ³Έν
type TupleEl = Tuple[number];
// ^? type TupleEl = string | number | Date
Value vs Type π€
π this
| | this | | --------- | ------------------- | | κ° value | μλ°μ€ν¬λ¦½νΈμ this | | νμ type | λ€νμ± this |
- valueλ‘ μ°μ΄λ thisλ? JSμ thisλ€.
- typeμΌλ‘ μ°μ΄λ thisλ? λ€νμ± this(polymorphic this)λ€.
polymorphic thisλ? μλ°μ€ν¬λ¦½νΈμ λ§μ°¬κ°μ§λ‘ νμ¬ μ€ν 컨ν μ€νΈλ₯Ό μ°Έμ‘°ν©λλ€.
μ¬λ¬ ν΄λμ€λ κ°μ²΄μμ 곡ν΅λ λ©μλλ₯Ό μ μνλ©΄μλ κ°κΈ° λ€λ₯Έ λμμ ꡬνν μ μλλ°,
μ£Όλ‘ μμκ³Ό μΈν°νμ΄μ€λ₯Ό ν΅ν΄ ꡬνλ©λλ€. μ μ°μ±μ νμ©!
π polymorphic this
- ν΄λμ€μ μμ, μ€λ²λΌμ΄λ©
class Animal {
move(distance: number) {
console.log(`${this.constructor.name} moved ${distance} meters.`);
// thisλ λ©μλλ₯Ό νΈμΆν κ°μ²΄μ΄λ€
}
}
class Dog extends Animal {
move(distance: number) {
console.log("Dog is running...");
super.move(distance);
}
}
class Bird extends Animal {
move(distance: number) {
console.log("Bird is flying...");
super.move(distance);
}
}
const dog = new Dog();
dog.move(10); // Dog is running... Dog moved 10 meters.
const bird = new Bird();
bird.move(20); // Bird is flying... Bird moved 20 meters.
- μΈν°νμ΄μ€, λ©μλ 체μ΄λ
interface Movable {
move(distance: number): this;
}
class Car implements Movable {
move(distance: number): this {
console.log(`Car drove ${distance} kilometers.`);
return this;
}
refuel(amount: number): this {
console.log(`Car refueled with ${amount} liters.`);
return this;
}
}
class Plane implements Movable {
move(distance: number): this {
console.log(`Plane flew ${distance} miles.`);
return this;
}
maintain(): this {
console.log("Plane underwent maintenance.");
return this;
}
}
const car = new Car();
const plane = new Plane();
car.move(100).refuel(50);
// Output:
// Car drove 100 kilometers.
// Car refueled with 50 liters.
plane.move(1000).maintain();
// Output:
// Plane flew 1000 miles.
// Plane underwent maintenance.
π &μ |
| | & | | | | --------- | ------------ | ------ | | κ° value | AND | OR | | νμ type | intersection | union |
π const
| | const | | --------- | ------------------------------------------- | | κ° value | const μ λ³μ μ μΈ | | νμ type | as const 리ν°λ΄ ννμμ μΆλ‘ λ νμ μ λ°κΏ |
π extends
- μλΈν΄λμ€ λλ μλΈνμ , μ λ€λ¦ νμ μ νμ μλ₯Ό μ μν μ μλ€.
- μλΈν΄λμ€
class Animal {
move() {
console.log("Moving along!");
}
}
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
- μλΈνμ
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: number;
}
const employee: Employee = {
name: "John",
age: 30,
employeeId: 1234,
};
- μ λ€λ¦μ νμ μ
function logLength<T extends { length: number }>(item: T): void {
console.log(item.length);
}
logLength("Hello, TypeScript!"); // 18
logLength([1, 2, 3, 4, 5]); // 5
logLength(123); // Error: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
π in
- loop λλ mapped νμ μ λ±μ₯
loop
-
λ°λ³΅λ¬Έμμ κ°μ²΄μ μμ±μ μνν λ μ¬μ©λ©λλ€. μ£Όλ‘ for...in 루νμ ν¨κ» μ¬μ©λλ©°, κ°μ²΄μ κ° μμ±μ λ°λ³΅νλ©΄μ μμ μ μνν©λλ€.
const obj = { a: 1, b: 2, c: 3 }; for (const key in obj) { console.log(`${key}: ${obj[key]}`); } // μΆλ ₯: // a: 1 // b: 2 // c: 3
mapped
-
Mapped νμ μμ in μ°μ°μλ μ‘°κ±΄λΆ νμ κ³Ό ν¨κ» μ¬μ©λ λ νμ λ³νμ μννλ λ° μ¬μ©λ©λλ€ (κ°μ²΄μ λͺ¨λ μμ±μ λ°λ³΅νλ©΄μ μλ‘μ΄ νμ μ λ§λ€κ±°λ μμ νλ λ° μ¬μ©)
type Optional<T> = { [K in keyof T]?: T[K]; // in μ°μ°μλ keyof Tλ₯Ό ν΅ν΄ Personμ λͺ¨λ μμ±μ λ°λ³΅ // κ° μμ±μ λν΄ ?:λ₯Ό μ¬μ©νμ¬ μ΅μ λ }; interface Person { name: string; age: number; } type OptionalPerson = Optional<Person>; // { name?: string; age?: number; }
νμ μ€ν¬λ¦½νΈκ° μ λμνμ§ μλλ€λ©΄?
- type space νμ 곡κ°κ³Ό value spaceλ₯Ό νΌλν΄μ μλͺ» μμ±νμ κ°λ₯μ±μ΄ ν¬λ€.
function email({
to: Person; // κ°μ κ΄μ μ ν΄μλμκΈ° λλ¬Έμ μλ¬!
subject: string;
body: string;
}) {
// ...
}
// κ°κ³Ό νμ
μ ꡬλΆνλ μ΄μ
function email(
{to, subject, body}: // κ°
{to: Person, subject: string, body: string} // νμ
) {
// ...
}
Things to Remember
- Know how to tell whether you're in type space or value space while reading a TypeScript expression. Use the TypeScript playground to build an intuition for this.
- νμ 곡κ°κ³Ό κ° κ³΅κ°μ ꡬλΆνλ λ°©λ²μ ν°λν΄μΌν¨
- νμ μ€ν¬λ¦½νΈ νλ μ΄κ·ΈλΌμ΄λ https://www.typescriptlang.org/play/?#code/
- Every value has a static type, but this is only accessible in type space. Type space constructs such as type and interface are erased and are not accessible in value space.
- λͺ¨λ κ°μ νμ μ κ°μ§μ§λ§, μ΄ νμ μ νμ 곡κ°μμλ§ μ κ·Όν μ μλ€.
- νμ
곡κ°μμ λ§λ νμ
, μΈν°νμ΄μ€λ κ° κ³΅κ°μμλ μμ λλ€.
- typeκ³Ό interfaceλ type spaceμ μ‘΄μ¬
- Some constructs, such as class or enum, introduce both a type and a value.
- classμ enumμ νμ κ³Ό κ°μΌλ‘ λ λ€ μ¬μ©λ μ μλ€
- typeof, this, and many other operators and keywords have different meanings in type space and value space.
- typeof, this λ±μ λ§μ μ°μ°μμ ν€μλλ => type spaceμ value spaceμμ λ€λ₯Έ λͺ©μ μΌλ‘ μ¬μ©λ μ μλ€
μμ΄ν 9 νμ λ¨μΈλ³΄λ€λ νμ μ μΈμ μ¬μ©νκΈ° Prefer Type Annotations to Type Assertions
- μ? νμ λ¨μΈμ νμ 체컀μκ² μ€λ₯λ₯Ό 무μνλΌκ³ νλ κ²μ΄κΈ° λλ¬Έμ΄λ€
interface Person {
name: string;
}
// Type Annotationsμ ν λΉλ κ°μ΄ ν΄λΉ μΈν°νμ΄μ€λ₯Ό λ§μ‘±νλμ§
const alice: Person = { name: "Alice" };
// ^? const alice: Person
// Type Assertionsλ μΆλ‘ ν νμ
μ΄ μλλΌλ, ν΄λΉ νμ
μΌλ‘ κ°μ£Όνλ€
const bob = { name: "Bob" } as Person; // Type Assertions
// ^? const bob: Person
μμ¬ μμ± μ²΄ν¬ excess property checkingλ μ μ©λμ§ μμ
const alice: Person = {
name: "Alice",
occupation: "TypeScript developer",
// ~~~~~~~~~ Object literal may only specify known properties,
// and 'occupation' does not exist in type 'Person'
};
const bob = {
name: "Bob",
occupation: "JavaScript developer",
} as Person; // μλ¬κ° μμ!
μ΄μ μ νμ λ¨μΈμ μ¬μ©νλ μ½λ νν
const bob = <Person>{};
- νμ¬λ tsxμ μ»΄ν¬λνΈ νκ·Έλ‘ μΈμλκΈ° λλ¬Έμ μ°μ΄μ§ μλλ€.
νμ΄ν ν¨μ
- μ΅μ’ μ μΌλ‘ μνλ νμ μ μ§μ λͺ μνκ³ , νμ μ€ν¬λ¦½νΈκ° ν λΉλ¬Έμ μ ν¨μ±μ κ²μ¬νλλ‘ ν¨
- λ¨, ν¨μ νΈμΆ 체μ΄λμ΄ μ°μλλ κ³³μ, 체μ΄λ μμλΆν° λͺ λͺ λ νμ μ κ°μ ΈμΌ νλ€ (μ νν κ³³μ μ€λ₯ νκΈ°νκΈ° μν¨)
interface Person {
name: string;
}
// νμ
λ¨μΈ
const people = ["alice", "bob", "jan"].map((name) => ({ name } as Person)); // Type is Person[]
// λ¨μΈ μ λ°νμ μ€λ₯ λ°μ
const wrongPeople = ["alice", "bob", "jan"].map((name) => ({} as Person)); // Type is Person[]
// μ΅μ’
μ μΌλ‘ μνλ νμ
μ μ§μ λͺ
μνκ³ , νμ
μ€ν¬λ¦½νΈκ° ν λΉλ¬Έμ μ ν¨μ±μ κ²μ¬νλλ‘ ν¨
const goodPeople: Person[] = ["alice", "bob", "jan"].map((name) => ({ name })); // OK
Type Assertionμ΄ νμν κ²½μ°
- νμ
μ²΄μ»€κ° μΆλ‘ ν νμ
보λ€, κ°λ°μκ° νλ¨ν νμ
μ΄ λ μ νν λ
- ex) DOM element
document.querySelector("#myButton")?.addEventListener("click", (e) => {
e.currentTarget;
// ^? (property) Event.currentTarget: EventTarget | null
// currentTarget is #myButton is a button element: νμ
μ€ν¬λ¦½νΈκ° μμ§ λͺ»νλ μ 보, why? DOMμ μ κ·Όν μ μκΈ° λλ¬Έμ
const button = e.currentTarget as HTMLButtonElement;
// ^? const button: HTMLButtonElement
});
nonNullAssertion: !
- λ³μμ μ λμ¬λ‘ μ°μΈ !λ? booleanμ λΆμ λ¬Έ
- μ λ―Έμ¬λ‘ μ°μΈ !λ? κ·Έ κ°μ΄ Nullμ΄ μλλΌλ λ¨μΈλ¬ΈμΌλ‘ ν΄μλλ€.
- μ£Όμ
- λ¨μΈλ¬Έμ μ»΄νμΌ κ³Όμ μμ μ κ±°λλ€.
- νμ
체컀λ μμ§ λͺ»νμ§λ§, κ°μ΄ nullμ΄ μλμ νμ ν μ μμ λ μ¬μ©νλ€
- νμ ν μ μλ€λ©΄ 쑰건문μΌλ‘ null μ²΄ν¬ ν΄μΌνλ€.
νμ λ¨μΈμ μ¬μ©ν νμ λ³νκ³Ό unknown
interface Person {
name: string;
}
const body = document.body;
const el = body as Person;
// ~~~~~~~~~~~~~~
// Conversion of type 'HTMLElement' to type 'Person' may be a mistake because
// neither type sufficiently overlaps with the other. If this was intentional,
// convert the expression to 'unknown' first.
-
μλ‘μ μλΈ νμ μ΄ μλκΈ° λλ¬Έμ λ³νμ΄ λΆκ°λ₯νλ€.
-
λ§μ½ unknown νμ μ μ¬μ©ν΄ μμμ νμ κ°μ λ³νμ΄ κ°λ₯ν, νμ λμνλ λ¨μΈμ μ¬μ©νλ κ²½μ°? 무μΈκ° μνν λμμ νκ³ μμμ μ μ μμ
-
λͺ¨λ νμ μ unknownμ μλΈνμ μ΄κΈ° λλ¬Έμ, unknown λ¨μΈμ νμ λμνλ€
const el = document.body as unknown as Person; // OK
-
Things to Remember
- Prefer type annotations (: Type) to type assertions (as Type).
- λ¨μΈλ³΄λ€ μ μΈμ μ¬μ©νμ
- Know how to annotate the return type of an arrow function.
- νμ΄ν ν¨μμ λ°ν νμ λͺ μνλ λ² μκΈ°
- Use type assertions and non-null assertions only when you know something about types that TypeScript does not.
- νμ μ€ν¬λ¦½νΈλ³΄λ€ λ μ μκ³ μλ κ² νμ€ν μν©μμλ§ νμ λ¨μΈκ³Ό non-null λ¨μΈμ μ°μ.
- When you use a type assertion, include a comment explaining why it's valid. // μ’μ νμ μ΅κ΄
// English
mnemonic [mneΒ·monΒ·ic] μ°μ κΈ°νΈ
intuition [inΒ·tuΒ·iΒ·tion] the ability to understand something immediately, without the need for conscious reasoning.
exclamation mark [exΒ·claΒ·maΒ·tion mark] !