2024-06-15.md
๐กDIL: ์ดํํฐ๋ธ ํ์ ์คํฌ๋ฆฝํธ
์คํฐ๋: ์๊ฐ CS, https://github.com/monthly-cs/2024-05-effective-typescript
์์ฑ์ผ: 2024-06-15
์์ฑ์: dusunax
์์ดํ 49: ์ฝ๋ฐฑ์ this์ ๋ํ ํ์ ์ ๊ณตํ๊ธฐ Provide a Type for this in Callbacks if It's Part of Their API
-
JS์ this๋ ๋ค์ด๋๋ฏน ์ค์ฝํ๋ค. ๋์ ์ค์ฝํ
-
let, const๋ lexical scope ๋ณ์์ ์ ํจ ๋ฒ์๊ฐ ์ฝ๋ ์์ฑ ์์ ์ ์์น์ ์ํด ๊ฒฐ์ ๋๋ ๋ฐฉ์ (์ ์ธ๋ ์์น์ ๋ฐ๋ผ๋ผ ์ค์ฝํ ๊ฒฐ์ )
-
this๋ ์ด๋์์ ํธ์ถ๋์๋์ ๋ฐ๋ผ๋ผ ๋ฌ๋ผ์ง๋ค.
-
์ธ์คํด์ค ์ฐธ์กฐํ๋ ํด๋์ค์์ ๊ฐ์ฅ ๋ง์ด ์ฐ์
class C { vals = [1, 2, 3]; logSquares() { for (const val of this.vals) { console.log(val ** 2); } } } const c = new C(); c.logSquares(); const c = new C(); const method = c.logSquares; // ๋ฉ์๋๋ฅผ ์ธ๋ถ ๋ณ์์ ๋ฃ๊ธฐ method(); // c.logSquares() // ์ธ์คํด์ค์ prototype์ logSquares ๋ฉ์๋ ์คํ // this์ ๊ฐ์ c๋ก ๋ฐ์ธ๋ฉ, this ์ฐธ์กฐ: ์ธ์คํด์ค์ this๋ ์์ฑ๋ ๊ฐ์ฒด์ this // c.logSquares๋ฅผ ์ฐธ์กฐํ๋ ๋ณ์๋ฅผ ์ฌ์ฉ << ํจ์์ this๋ ์ ์ญ this (strictmode์์ undefined) method.call(c); // call๋ก this ๋ฐ์ธ๋ฉ
-
-
-
prototype
class ResetButton { constructor() { // this.onClick ๋ฉ์๋์ ์ปจํ ์คํธ๋ฅผ ํ์ฌ ์ธ์คํด์ค๋ก ๋ฐ์ธ๋ฉํฉ๋๋ค. this.onClick = this.onClick.bind(this); } render() { // makeButton ํจ์๋ฅผ ํธ์ถํ์ฌ ๋ฒํผ์ ์์ฑํฉ๋๋ค. // ๋ฒํผ์ ํ ์คํธ๋ 'Reset'์ด๊ณ , ํด๋ฆญ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ this.onClick์ผ๋ก ์ค์ ๋ฉ๋๋ค. return makeButton({ text: "Reset", onClick: this.onClick }); } onClick() { // ๋ฒํผ ํด๋ฆญ ์ ์คํ๋๋ ํจ์์ ๋๋ค. // alert ์ฐฝ์ 'Reset'๊ณผ ํ์ฌ ์ธ์คํด์ค๋ฅผ ๋ฌธ์์ด๋ก ๋ณํํ์ฌ ํ์ํฉ๋๋ค alert(`Reset ${this}`); } }
-
lookup sequence
- ํ๋กํ ํ์ ์ฒด์ธ ์ฌ๋ผ๊ฐ๊ธฐ ์ ์ ๋ฐ์ธ๋ฉ๋ ํจ์ ์ฐธ์กฐ
this ๋ฐ์ธ๋ฉ๊ณผ ๊ด๋ จ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ฝ๋ฐฑํจ์๋ฅผ ํ์ดํ ํจ์๋ก ์ฌ์ฉํ๋ ํจํด
- ํ์ดํ ํจ์๋ ์์ ์ค์ฝํ์ this๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ this ๋ฐ์ธ๋ฉ ๋ฌธ์ ๋ฅผ ํํผํ ์ ์๋ค.
- ํ์ ์คํฌ๋ฆฝํธ๋ JS์ this ๋ฐ์ธ๋ฉ์ ๊ทธ๋๋ก ๋ชจ๋ธ๋งํ๋ค => this๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋ฐฑํจ์๊ฐ ์๋ค๋ฉด ๊ณ ๋ คํด์ผํจ
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์์ ์ฝ๋ฐฑ ํจ์์์ this๋ฅผ ์ฐธ์กฐํ ์ ์๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์๊ฐ ์ฝ๋ฐฑํจ์๋ฅผ ํ์ดํ ํจ์๋ก ์์ฑํ๋ฉด ํ์ ์คํฌ๋ฆฝํธ๊ฐ ์๋ฌ๋ฅผ ์ก์๋
class ResetButton {
render() {
return makeButton({text: 'Reset', onClick: this.onClick});
},
onClick=()=>{
alert(`Reset ${this}`)
}
}
Things to Remember
- Understand how this binding works.
- this ๋ฐ์ธ๋ฉ ~
- Provide a type for this in callbacks if it's part of your API.
- API ๋ง๋ค ๋ ์ฝ๋ฐฑํจ์์ this ํ์ ์ ๊ณตํ๊ธฐ
- Avoid dynamic this binding in new APIs.
- ๋์ ๋ฐ์ธ๋ฉ ํผํ๊ธฐ: ํ์ดํ ํจ์, ๋ช ์์ ๋ฐ์ธ๋ฉ bind(), ์ฝ๋ฐฑ ํจ์๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ์์ this๋ฅผ ์ฐธ์กฐํ์ง ์๋๋ก ์ค๊ณ
์์ดํ 50: ์ค๋ฒ๋ก๋ฉ ํ์ ๋ณด๋ค๋ ์กฐ๊ฑด๋ถ ํ์ ์ ์ฌ์ฉํ๊ธฐ Prefer Conditional Types to Overload Signatures
ํจ์ ์ค๋ฒ๋ก๋ฉ
- ๋ชจํธํ๊ฑฐ๋(์ ๋์จ) ๊ณผํ๊ฒ ๊ตฌ์ฒด์ (์ ๋๋ฆญ)
// โ ๋๋ฌด ๋ชจํธํจ
declare function double(x: string | number): string | number;
const num = double(12);
// ^? const num: string | number
const str = double("x");
// ^? const str: string | number
// โ ์๋
declare function double<T extends string | number>(x: T): T;
const num = double(12);
// ^? const num: 12
const str = double("x");
// ^? const str: "x"
- ๋๋ ์ค๋ฅ
declare function double(x: number): number;
declare function double(x: string): string;
function f(x: string | number) {
return double(x);
// ~ Argument of type 'string | number' is not assignable
// to parameter of type 'string'
// โ ์ค๋ฒ๋ก๋ฉ ํ์
์ค์์ ์ผ์นํ๋ ํ์
์ ์ฐพ์ ๋๊น์ง ์์ฐจ์ ์ผ๋ก ๊ฒ์ํ๊ธฐ ๋๋ฌธ์ string | number์ ์ผ์นํ์ง ์์ผ๋ฏ๋ก ์ค๋ฅ
// string | number์ number์ ํ ๋นํ ์ ์๋ค.
// string | number์ string์ ํ ๋นํ ์ ์๋ค.
// ํ ๋นํ ์ ์๋ค
}
์กฐ๊ฑด๋ถ ํ์ ์ฌ์ฉํ๊ธฐ
- ์ ๋๋ฆญ์ ์ด๋ ๊ฒ ์ฌ์ฉํ ์ ์๋ค!
- ์ ๋์จ์ ์กฐ๊ฑด๋ถ ํ์ ์ ์ ์ฉํ๋ฉด? ์กฐ๊ฑด๋ถ ํ์ ์ ์ ๋์จ์ผ๋ก ๋ถ๋ฆฌ๋๋ค.
function double<T extends string | number>( // T๋ string | number
x: T // x๋ T
): T extends string ? string : number; // ๊ทธ๋ฐ๋ฐ? T๊ฐ string์ด๋ฉด string ๋ฆฌํด, ์๋๋ฉด number
function double(x: string | number) {
// return x + x;
// ~~~~~ Operator '+' cannot be applied to types 'string | number' and 'string | number'.ts(2365)
// (parameter) x: string | number
return typeof x === "string" ? x + x : x + x;
}
Things to Remember
- Prefer conditional types to overloaded type signatures. By distributing over unions, conditional types allow your declarations to support union types without additional overloads.
- ์ค๋ฒ๋ก๋ฉ ํ์ ๋ณด๋ค ์กฐ๊ฑด๋ถ ํ์ ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค. ์ถ๊ฐ์ ์ธ ์ค๋ฒ๋ก๋ฉ ์์ด ์ ๋์จ ํ์ ์ง์
- If the union case is implausible, consider whether your function would be clearer as two or more functions with different names.
- ์ ๋์ธ ์ผ์ด์ค๊ฐ ๊ฐ๋ฅ์ฑ์ด ๋ฎ๋ค๋ฉด, ํจ์์ ๋ช ํ์ฑ์ ์ํด ๋ ๊ฐ ์ด์์ ๋ค๋ฅธ ์ด๋ฆ์ ๊ฐ์ง ํจ์๋ก ๋๋๋ ๊ฒ ๊ณ ๋ คํ๊ธฐ
- Consider using the single overload strategy for implementing functions declared with conditional types.
- ๋จ์ผ ํจ์ ๊ตฌํํ๊ธฐ + ์กฐ๊ฑด๋ถ ํ์
์์ดํ 51: ์์กด์ฑ ๋ถ๋ฆฌ๋ฅผ ์ํด ๋ฏธ๋ฌ ํ์ ์ฌ์ฉํ๊ธฐ Mirror Types to Sever Dependencies
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด ํ์
์์: NodeJS ํ๊ฒฝ์์ ์
๋ ฅ๋ฐ์ Buffer ํ์
์ ์ํด
@type/node
๋ฅผ devDependency๋ก ํฌํจํ ๊ฒฝ์ฐ- Buffer ํ์ ์ NodeJS ๊ฐ๋ฐ์๋ง ํ์ํ ํ์
- NodeJS์ ๋ฌด๊ดํ ์น ๊ฐ๋ฐ์: NodeJS ํ์์์
- JS ๊ฐ๋ฐ์: @types ํ์์์
- ์ฌ์ฉํ์ง ์๋ ๋ชจ๋ => ๋ฏธ๋ฌ๋งํ ํ์
์ธํฐํ์ด์ค ๋ง๋ค๊ธฐ
- ํ์ํ ์ ์ธ๋ถ๋ง ์ถ์ถํ์ฌ ๋ช ์ํ๋ค!
- ๋ค๋ง, ํ๋ก์ ํธ ์์กด์ฑ์ด ๋ค์ํ๊ณ ํ์ ์์กด์ฑ์ด ๋ง์ ๊ฒฝ์ฐ. ํ์
์ ์ธ์ ๋๋ถ๋ถ ์ถ์ถํด์ผ ํ๋ค๋ฉด ๋ช
์์ ์ผ๋ก
@types
์์กด์ฑ์ ์ถ๊ฐํ๋ ๊ฒ์ด ๋์- ์ ๋ํ ์คํธ ๋ชจํนํ ๋๋ ์ฌ์ฉ
Things to Remember
- Avoid transitive type dependencies in published npm modules.
- ๊ณต๊ฐํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ์ด์ ํ์
์์กด์ฑ ํผํ๊ธฐ
- ๋ชจ๋ ์ฌ์ฉ์๊ฐ ์ง์ ์ฌ์ฉํ์ง ์๋ ๋ชจ๋์ ๋ํ ํ์ ์ ์ ์ค์นํด์ผํจ
- ๊ด๋ฆฌ & ์ถ์ ํด์ผ ํ ํจํค์ง ์๊ฐ ๋์ด๋๊ณ , ๋ฒ์ ์ถฉ๋ ๋ฌธ์ / ๋ฐฐํฌ ๋ฐ ์ ๋ฐ์ดํธ ๋ณต์กํด์ง
- ๊ณต๊ฐํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ์ด์ ํ์
์์กด์ฑ ํผํ๊ธฐ
- Use structural typing to sever dependencies that are nonessential.
- ํ์๊ฐ ์๋ ์์กด์ฑ์ ๊ตฌ์กฐ์ ํ์ดํ ์ฌ์ฉํ๊ธฐ
- Don't force JavaScript users to depend on @types. Don't force web developers to depend on Node.js.
- ์์กด์ฑ์ ์ถ๊ฐํ ๋, JS ๊ฐ๋ฐ์, Node.js ์์ฐ๋ ๊ฐ๋ฐ์์ ๊ฒฝ์ฐ ๊ณ ๋ คํ๊ธฐ
์์ดํ 52: ํ ์คํ ํ์ ์ ํจ์ ์ ์ฃผ์ํ๊ธฐ Write Tests for Your Types
- ํ๋ก์ ํธ๋ฅผ ๊ณต๊ฐํ ๋ ํ ๊ฒ: ํ
์คํธ ์ฝ๋ ์์ฑ, ํ์
์ ์ธ ํ
์คํธ
- ํจ์๋ฅผ ์คํํ๋ ๋ฐฉ์ => ๋ฐํ ํ์ ์ ์ฒดํฌํ๋ ๋ฐฉ์
- dtslint ๋๋ ํ์ ์์คํ ์ธ๋ถ์ ํ์ ์ ๊ฒ์ฌํ๋ ๋๊ตฌ๋ ์ฌ์ฉํ๊ธฐ
DefinitelyTyped ๋ฌธ์ ์ ๋์์ https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.ko.md
ํฌํผ ํจ์
- ๋ถํ์ํ ์ถ๊ฐ ๋ณ์๋ฅผ ๋ง์ด ๋ง๋ค๊ฑฐ๋, ๋ฆฐํ ๊ท์น์ ๋นํ์ฑํ์ง ์๊ธฐ ์ํด ์ฌ์ฉ
function assertType<T>(x: T) {}
assertType<number[]>(map(["john", "paul"], (name) => name.length)); // ์ด๋ฆ์ ๊ธธ์ด๋ฅผ ๋ฐฐ์ด๋ก ๋ฐํํ๋ ๊ธฐ๋ฅ์ ๋ฆฌํด ํ์
์ ๊ฒ์ฆ
assertType ํฌํผ ํจ์์ ๋ฌธ์ ์
- ํ ๋น ๊ฐ๋ฅ์ฑ์ ์ฒดํฌํ๊ธฐ ๋๋ฌธ์ ์ฌ๋ฒ๋ string ๋๋ number์ ํ ๋น ๊ฐ๋ฅ
const n = 12;
assertType<number>(n); // OK
- ๋งค๊ฐ๋ณ์ ๊ฐฏ์ ์ค๋ฅ (๋ ์ ์ ๋งค๊ฐ๋ณ์๋ฅผ ๊ฐ์ง ํจ์๋ฅผ ํ ๋นํ ์ ์์)
function assertType<T>(x: T) {}
const add = (a: number, b: number) => a + b;
assertType<(a: number, b: number) => number>(add); // OK
const double = (x: number) => 2 * x;
assertType<(a: number, b: number, c: number) => number>(double); // OK!?
๊ฐ์
- ํจ์์ ๋งค๊ฐ๋ณ์ ํ์ ๊ณผ ๋ฐํํ์ ์ ๋ถ๋ฆฌํ์ฌ ํ ์คํธ: Parameters, ReturnType
const double = (x: number) => 2 * x;
declare let p: Parameters<typeof double>;
assertType<[number, number]>(p); // ๋งค๊ฐ๋ณ์ ํ์
ํ
์คํธ
// ~ Argument of type '[number]' is not
// assignable to parameter of type [number, number]
declare let r: ReturnType<typeof double>;
assertType<number>(r); // OK
this ํ ์คํธ ํต๊ณผํ๋ ์์
const beatles = ["john", "paul", "george", "ringo"]; // string[]
assertType<number[]>(
map(beatles, function (name, i, array) {
// ~~~ Argument of type '(name: any, i: any, array: any) => any' is
// not assignable to parameter of type '(u: string) => any'
// ํ์
์คํฌ๋ฆฝํธ๋ ์ด ์ฝ๋ฐฑ ํจ์์ ํ์
์ด ์ฌ๋ฐ๋ฅด์ง ์๋ค๊ณ ๊ฒฝ๊ณ ํฉ๋๋ค.
assertType<string>(name); // name์ string
assertType<number>(i); // index๋ ์ซ์
assertType<string[]>(array); // array๋ ๋ฌธ์์ด ๋ฐฐ์ด
assertType<string[]>(this); // this๋ ์์์ any <- ์ฌ๊ธฐ์ ๋ฌธ์
// ~~~~ 'this' implicitly has type 'any'
return name.length; //๊ฐ ๋ฌธ์์ด์ ๊ธธ์ด๋ฅผ ๋ฐํํฉ๋๋ค.
})
);
declare function map<U, V>(
array: U[],
fn: (this: U[], u: U, i: number, array: U[]) => V
): V[];
// this๊ฐ U์ ๋ฐฐ์ด์์ ๋ช
์ํจ
dtslint
-
๋ชจ๋์ ์ ์ธ(declare)ํ๋ฉด ์ ์ฒด ๋ชจ๋์ any ํ์ ์ ํ ๋นํ๋ค.
- ํ ์คํธ๋ ์ ๋ถ ํต๊ณผํ์ง๋ง ๋ชจ๋ ํ์ ์์ ์ฑ์ ํฌ๊ธฐ.
- ํจ์ ํธ์ถ์ด ์์์ any๋ฅผ ๋ฐํ
-
DefinitelyTyped์ ํ์ ์ ์ธ์ ์ํ ๋๊ตฌ: dtslint
- ์ฃผ์์ ํตํด ๋์ํ๋ค.
- ๊ฐ ์ฌ๋ฒ์ ํ์
์ ์ถ์ถํ์ฌ "๊ธ์ ์์ฒด"๊ฐ ๊ฐ์ ์ง ๋น๊ต
- ํ๊ณ: ๊ธ์๋ง ๋น๊ตํ๋ค. string๊ณผ any, number|string๊ณผ string|number ํ์ธ ์ด๋ ค์
const beatles = ["john", "paul", "george", "ringo"]; map( beatles, function ( name, // $ExpectType string i, // $ExpectType number array // $ExpectType string[] ) { this; // $ExpectType string[] return name.length; } ); // $ExpectType number
expectTypeOf
- ํ์ ์ผ์น ํ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
import { expectTypeOf } from "expect-type";
const beatles = ["john", "paul", "george", "ringo"];
expectTypeOf(
map(beatles, function (name, i, array) {
expectTypeOf(name).toEqualTypeOf<string>();
expectTypeOf(i).toEqualTypeOf<number>();
expectTypeOf(array).toEqualTypeOf<string[]>();
expectTypeOf(this).toEqualTypeOf<string[]>();
return name.length;
})
).toEqualTypeOf<number[]>();
Things to Remember
- When testing types, be aware of the difference between equality and assignability, particularly for function types.
- ํ์ ํ ์คํธ ์, ํจ์ ํ์ ์์ ๋์ผ์ฑ(equality)๊ณผ assignability(ํ ๋น ๊ฐ๋ฅ์ฑ)์ ์ฐจ์ด์ ์๊ธฐ
- For functions that use callbacks, test the inferred types of the callback parameters. Don't forget to test the type of this if it's part of your API.
- ์ฝ๋ฐฑ ํ๋ผ๋ฏธํฐ์ ํ์ ํ ์คํธ. API์ this ๊ด๋ จ ์ฌํญ ์์ผ๋ฉด ํ ์คํธ
- Avoid writing your own type testing code. Use one of the standard tools instead.
- ์ง์ ํ ์คํธ ์ฝ๋ ์์ฑํ๋ ๊ฒ๋ณด๋ค ์ผ๋ฐํ๋ ํด ์ฌ์ฉํ๊ธฐ
- For code on DefinitelyTyped, use dtslint. For your own code, use vitest, expect-type, or the Type Challenges approach. If you want to test type display, use eslint-plugin-expect-type.
- DefinitelyTyped์์๋ dtslint ์ฌ์ฉ. ์ง์ ์์ฑํ๋ ์ฝ๋๋ผ๋ฉด ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์.
- ๋๊ตฌ๋ค: vitest, expect-type, Type Challenges ์ ๊ทผ๋ฒ, (ํ์ ๋์คํ๋ ์ด)eslint-plugin-expect-type