Skip to content

Item 69: Provide a Type for this in Callbacks if It's Part of Their API

要点

  • Understand how this binding works.
  • Provide a type for this in callbacks if it's part of your API.
  • Avoid dynamic this binding in new APIs.
  • 理解 this 绑定是如何工作的。
  • 如果 this 是你 API 的一部分,在回调中提供 this 的类型。
  • 避免在新 API 中使用动态 this 绑定。

正文

ts
class C {
  vals = [1, 2, 3]
  logSquares() {
    for (const val of this.vals) {
      console.log(val ** 2)
    }
  }
}

const c = new C()
c.logSquares()

💻 playground


ts
const c = new C()
const method = c.logSquares
method()

💻 playground


ts
const c = new C()
const method = c.logSquares
method.call(c) // Logs the squares again

💻 playground


ts
document.querySelector('input')?.addEventListener('change', function (e) {
  console.log(this) // Logs the input element on which the event fired.
})

💻 playground


ts
class ResetButton {
  render() {
    return makeButton({ text: 'Reset', onClick: this.onClick })
  }
  onClick() {
    alert(`Reset ${this}`)
  }
}

💻 playground


ts
class ResetButton {
  constructor() {
    this.onClick = this.onClick.bind(this)
  }
  render() {
    return makeButton({ text: 'Reset', onClick: this.onClick })
  }
  onClick() {
    alert(`Reset ${this}`)
  }
}

💻 playground


ts
class ResetButton {
  render() {
    return makeButton({ text: 'Reset', onClick: this.onClick })
  }
  onClick = () => {
    alert(`Reset ${this}`) // "this" refers to the ResetButton instance.
  }
}

💻 playground


js
class ResetButton {
  constructor() {
    this.onClick = () => {
      alert(`Reset ${this}`) // "this" refers to the ResetButton instance.
    }
  }
  render() {
    return makeButton({ text: 'Reset', onClick: this.onClick })
  }
}

💻 playground


ts
function addKeyListener(
  el: HTMLElement,
  listener: (this: HTMLElement, e: KeyboardEvent) => void
) {
  el.addEventListener('keydown', (e) => listener.call(el, e))
}

💻 playground


ts
function addKeyListener(
  el: HTMLElement,
  listener: (this: HTMLElement, e: KeyboardEvent) => void
) {
  el.addEventListener('keydown', (e) => {
    listener(el, e)
    //           ~ Expected 1 arguments, but got 2
  })
}

💻 playground


ts
function addKeyListener(
  el: HTMLElement,
  listener: (this: HTMLElement, e: KeyboardEvent) => void
) {
  el.addEventListener('keydown', (e) => {
    listener(e)
    // ~~~~~~~~ The 'this' context of type 'void' is not assignable
    //          to method's 'this' of type 'HTMLElement'
  })
}

💻 playground


ts
declare let el: HTMLElement
addKeyListener(el, function (e) {
  console.log(this.innerHTML)
  //          ^? this: HTMLElement
})

💻 playground


ts
class Foo {
  registerHandler(el: HTMLElement) {
    addKeyListener(el, (e) => {
      console.log(this.innerHTML)
      //               ~~~~~~~~~ Property 'innerHTML' does not exist on 'Foo'
    })
  }
}

💻 playground

Released under the MIT License.