Skip to content

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'

💻 playground


ts
;(document as any).monkey = 'Tamarin' // OK

💻 playground


ts
;(document as any).monky = 'Tamarin' // Also OK, misspelled
;(document as any).monkey = /Tamarin/ // Also OK, wrong type

💻 playground


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...
}

💻 playground


ts
declare global {
  interface Window {
    /** The currently logged-in user */
    user: User
  }
}

💻 playground


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
}

💻 playground


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'.
}

💻 playground


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'.
}

💻 playground

Released under the MIT License.