Item 47: Prefer Type-Safe Approaches to Monkey Patching
要点
- Prefer structured code to storing data in globals or on the DOM.
- If you must store data on built-in types, use one of the type-safe approaches (augmentation or asserting a custom interface).
- Understand the scoping issues of augmentations. Include
undefined
if that's a possibility at runtime. - 优先使用结构化代码,而不是将数据存储在全局变量或 DOM 上。
- 如果必须将数据存储在内建类型上,使用类型安全的方法(如扩展或断言自定义接口)。
- 理解扩展的作用域问题。如果在运行时可能出现
undefined
,则需要在类型中包含undefined
。
正文
ts
document.monkey = 'Tamarin'
// ~~~~~~ Property 'monkey' does not exist on type 'Document'
ts
;(document as any).monkey = 'Tamarin' // OK
ts
;(document as any).monky = 'Tamarin' // Also OK, misspelled
;(document as any).monkey = /Tamarin/ // Also OK, wrong type
ts
interface User {
name: string
}
document.addEventListener('DOMContentLoaded', async () => {
const response = await fetch('/api/users/current-user')
const user = (await response.json()) as User
window.user = user
// ~~~~ Property 'user' does not exist
// on type 'Window & typeof globalThis'.
})
// ... elsewhere ...
export function greetUser() {
alert(`Hello ${window.user.name}!`)
// ~~~~ Property 'user' does not exist on type Window...
}
ts
declare global {
interface Window {
/** The currently logged-in user */
user: User
}
}
ts
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
}
ts
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'.
}
ts
type MyWindow = typeof window & {
/** The currently logged-in user */
user: User | undefined
}
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'.
}