2024-06-07.md
π‘DIL: μ΄νν°λΈ νμ μ€ν¬λ¦½νΈ
μ€ν°λ: μκ° CS, https://github.com/monthly-cs/2024-05-effective-typescript
μμ±μΌ: 2024-06-07
μμ±μ: dusunax
μμ΄ν 43: λͺ½ν€ ν¨μΉλ³΄λ€λ μμ ν νμ μ μ¬μ©νκΈ° Prefer Type-Safe Approaches to Monkey Patching
- μλ°μ€ν¬λ¦½νΈμ νΉμ§ μ€ νλ: κ°μ²΄μ ν΄λμ€μ μμμ μμ±μ μΆκ°ν μ μλ€.
- μ μ°νλ€.
- window, documentμ μ μ λ³μ λ§λ€κΈ°
- const el = document.getElementById('colobus'); el.home = 'tree';
- νλ‘ν νμ
μλ μΆκ°ν μ μλ€.
- νλ‘ν νμ μ μμ±μ μΆκ°νλ©΄ ν΄λΉ μμ±μ΄ λͺ¨λ μΈμ€ν΄μ€μ μμλμ΄ λ²λ¦Ό
RegExp.prototype.monkey = "Capuchin"; /123/.monkey;
μ μ κ°μ²΄ μΆκ°νκΈ°
document.monkey = "Tamarin";
// ~~~~~~ Property 'monkey' does not exist on type 'Document'
(document as any).monkey = "Tamarin"; // μ μ! νμ§λ§ μμ νμ§ μλ€.
document.addEventListener("DOMContentLoaded", async () => {
const response = await fetch("/api/users/current-user");
const user = (await response.json()) as User;
window.user = user; // OK
});
// ... elsewhere ...
export function greetUser() {
alert(`Hello ${window.user.name}!`); // OK
}
μ΄λ»κ² ν κΉ? A. 보κ°!
// TypeScriptμμ Document μΈν°νμ΄μ€λ₯Ό μ μΈνκ³ monkey μμ±μ μΆκ°
interface Document {
monkey: string;
}
// ### κΈλ‘λ² λ²μμμ Document μΈν°νμ΄μ€λ₯Ό 보κ°
// Document μΈν°νμ΄μ€λ₯Ό νμ₯νμ¬ μ μ document κ°μ²΄μ monkey μμ±μ μΆκ°ν μ μλλ‘ νλ€.
// ESModule κ΄μ μμ μ λλ‘ λμνλ €λ©΄ global μ μΈμ΄ μμ΄μΌ νλ€.
// ### λͺ¨λ μ€μ½νμ κΈλ‘λ² μ€μ½νμ μ°¨μ΄
// ESModulesμμλ κ° λͺ¨λμ΄ μ체μ μΈ μ€μ½νλ₯Ό κ°μ§λ―λ‘, μ μ κ°μ²΄μ μμ±μ μΆκ°νλ €λ©΄ μ΄λ₯Ό λͺ
μμ μΌλ‘ μ μΈν΄μΌ νλ€.
declare global {
interface Document {
monkey: string;
}
}
document.monkey = "Tamarin";
μ₯μ
- νμ μ΄ λ μμ νλ€. (μ€ν, μλͺ»λ ν λΉ)
- μμ±μ μ£Όμμ λΆμΌ μ μλ€.
- μμ±μ μλ μμ±μ μ¬μ©ν μ μλ€.
- λͺ½ν€ ν¨μΉκ° μ΄λ€ λΆλΆμ μ μ©λμλμ§ μ νν κΈ°λ‘μ΄ λ¨λλ€.
λ¨μ
- 보κ°μ μ μμ μΌλ‘ μ μ©λμ΄, μ½λμ λ€λ₯Έ λΆλΆμ΄λ λΌμ΄λΈλ¬λ¦¬λ‘λΆν° λΆλ¦¬ν μ μλ€. (ν° λ¨μ )
- μ ν리μΌμ΄μ μ΄ μ€νλλ λμμ μμ±μ ν λΉνλ©΄, μ€ν μμ μ 보κ°μ μ μ©ν μ μλ€. HTMLElementλ€μ μ‘°μν κ²½μ° μ΄λ€ μμ±μ΄ μκ±°λ μλ€λ©΄ λ¬Έμ κ° λλ€. string | undefinedλ‘ μ μΈν κ²½μ° λΆνΈν΄μ§λ€.
declare global {
interface Window {
/** The currently logged-in user */
user: User | undefined;
}
}
// ...
export function greetUser() {
alert(`Hello ${window.user.name}!`);
// ~~~~~~~~~~~ 'window.user' is possibly 'undefined'.
}
B. ꡬ체μ μΈ νμ λ¨μΈλ¬Έ
- νμ₯ν΄μ λ¨μΈνλ κ²½μ°! π
type MyWindow = typeof window & {
/** The currently logged-in user */
user: User | undefined;
};
// νμ
λ¨μΈλ¬Έμ μ μμ΄κ³ , ν λΉλ¬Έμ νμ
μ μμ νλ€.
// Document νμ
μ 건λλ¦¬μ§ μκ³ , λ³λλ‘ νμ₯νλ μλ‘μ΄ νμ
μ λμ
// λͺ¨λ μμ λ¬Έμ κ° ν΄κ²°λμλ€. (importνλ κ³³μ μμλ§ ν΄λΉλ¨)
// νμ§λ§~ λͺ½ν€ ν¨μΉλ₯Ό λ¨μ©ν΄μλ μλλλ©°, κΆκ·Ήμ μΌλ‘ λ μ μ€κ³λ κ΅¬μ‘°λ‘ λ¦¬ν©ν°λ§νλ κ²μ΄ μ’μ΅λλ€.
document.addEventListener("DOMContentLoaded", async () => {
const response = await fetch("/api/users/current-user");
const user = (await response.json()) as User;
(window as MyWindow).user = user; // OK
});
// ...
export function greetUser() {
alert(`Hello ${(window as MyWindow).user.name}!`);
// ~~~~~~~~~~~~~~~~~~~~~~~~~ Object is possibly 'undefined'.
}
Things to Remember
- Prefer structured code to storing data in globals or on the DOM.
- μ μ λ³μλ DOMμ λ°μ΄ν°λ₯Ό μ μ₯νμ§ μκ³ , λ°μ΄ν°λ₯Ό λΆλ¦¬ν΄μ μ¬μ©νλ κ²μ μ§ν₯νμ.
- If you must store data on built-in types, use one of the type-safe approaches (augmentation or asserting a custom interface).
- λ΄μ₯ νμ μ λ°μ΄ν°λ₯Ό μ μ₯ν΄μΌ νλ κ²½μ°, μμ ν νμ μ κ·Όλ² μ€ νλλ₯Ό μ¬μ©νλ€. (λ³΄κ° augmentation, μ¬μ©μ μ μ μΈν°νμ΄μ€ λ¨μΈ assering a custom interface)
- Understand the scoping issues of augmentations. Include undefined if that's a possibility at runtime.
- λͺ¨λ μμ λ¬Έμ λ₯Ό μ΄ν΄νμ. λ°νμμ undefinedμΌ κ°λ₯μ±μ΄ μλ€λ©΄? ν¬ν¨νμ.
μμ΄ν 44: νμ 컀λ²λ¦¬μ§λ₯Ό μΆμ νμ¬ νμ μμ μ± μ μ§νκΈ° Item 49: Track Your Type Coverage to Prevent Regressions in Type Safety
λͺ μμ any νμ
- any νμ
μ μ’νκΈ° μν΄ λͺ
μμ μΌλ‘ μ¬μ©ν anyλ, μΈλ±μ€λ₯Ό μμ±νλ©΄ λ¨μ anyκ° λμ΄ μ½λ μ λ°μ μ€μΌμν΄
- any[], {[key:string]:any}, Record<string, any>
- κ·Έλ λ€λ©΄ κ²°κ΅ μ μ°λ κ²?? unknownμ μ¬μ©νλ κ²μ΄ μ’κ² λ€.
μλνν° any νμ μ μΈ
@types
μ μΈ νμΌλ‘λΆν° any νμ μ΄ μ νλκΈ° λλ¬Έμ, μ½λ μ λ°μ μν₯μ λ―ΈμΉλ€.- noImplicitAnyλ₯Ό μ€μ νκ³ , any μμ±ν μ μλλΌλ λ§μ°¬κ°μ§~
any μΌμ΄μ€
// A. μ΄λ€ μ’
λ₯μ column μ 보λ₯Ό λ§λλ ν¨μ
function getColumnInfo(name: string): any {
return utils.buildColumnInfo(appState.dataSchema, name); // Returns any
}
// B. νμ
μ λ³΄κ° λͺ¨λ μ κ±°λ μν
// βοΈ μμ μ½λ νμΈμ΄ μλ¨
// β myModule.d.ts κ° μμΌλ©΄ μ€λ₯
declare module "my-module";
// ~~~~~~~~~~ Invalid module name in augmentation, module 'my-module' cannot be found.
// β μ€μ λ‘ μ‘΄μ¬νλ λΌμ΄λΈλ¬λ¦¬λ₯Ό declare μ, νμ
μ²΄μ»€κ° μΌν¨
declare module "@next/mdx";
import { notMethod } from "@next/mdx";
// ~~~~~~~~~ Module '"@next/mdx"' has no exported member 'notMethod'
notMethod();
// β myModule.d.tsκ° μμΌλ©΄ νμ
μ²΄μ»€κ° μΌν¨.
// myModule.d.ts
declare module "my-module" {
export function someMethod(): void;
}
// λ€λ₯Έ νμΌ
declare module "my-module";
import { someMethod, notMethod } from "my-module";
// ~~~~~~~~~ Module '"my-module"' has no exported member 'notMethod'.
someMethod();
notMethod();
// C. νμ
μ λ²κ·Έκ° μλ κ²½μ°
// μ λμ¨ νμ
μ λ°ννλλ‘ μ μΈνκ³ , μ€μ λ‘λ νΉμ ν κ°μ λ°ννλ κ²½μ°
// μ μΈλ νμ
κ³Ό μ€μ λ°νλ νμ
μ΄ λ§μ§ μλ κ²½μ°?
// anyλ‘ λ¨μΈλ¬Έμ μ¬μ©ν΄μΌ νλ€.
npx type-coverage
anyμ κ°―μλ₯Ό μΆμ νκΈ°
- ν°λ―Έλμμ percentage(λ°±λΆμ¨)λ₯Ό νμΈν μ μλ λΌμ΄λΈλ¬λ¦¬
$ npx type-coverage --detail // any νμ
μ λͺ¨λ μΆλ ₯νλ μ΅μ
- anyκ° λ μ΄μ νμ μκ±°λ, μ€νλμ§ μλ μ½λμΌ μλ μμΌλ―λ‘ κΎΈμ€ν νμ 컀λ²λ¦¬μ§λ₯Ό μΆμ νμ¬ μ κ²νλ κ²μ΄ μ’μ
Things to Remember
- Even with noImplicitAny set, any types can make their way into your code either through explicit anys or third-party type declarations (@types).
- noImplicitAnyκ° μ€μ λμ΄ μμ΄λ λͺ μμ any, μλνν° νμ μ μΈμ ν΅ν΄ μ½λ λ΄μ μ‘΄μ¬ν μ μλ€.
- Consider tracking how well-typed your program is using a tool like type-coverage. This will encourage you to revisit decisions about using any and increase type safety over time.
- μμ±ν νλ‘κ·Έλ¨μ νμ
μ΄ μΌλ§λ μ μ μΈλμλ μ§ μΆμ ν¨μΌλ‘μ => anyμ μ¬μ©μ μ€μ¬λκ°λ©° νμ
μμ μ±μ λμΌ μ μλ€.
- μΆμ λΌμ΄λΈλ¬λ¦¬ μμ: type-coverage
- μμ±ν νλ‘κ·Έλ¨μ νμ
μ΄ μΌλ§λ μ μ μΈλμλ μ§ μΆμ ν¨μΌλ‘μ => anyμ μ¬μ©μ μ€μ¬λκ°λ©° νμ
μμ μ±μ λμΌ μ μλ€.
// μμ΄
augmentation 보κ°