React 类组件中几乎所有的 this 指向总结

91 阅读5分钟

在 Hooks 出来之前类组件还是主流,但是 Hooks 出来之后,函数组件差不多成为了主流,其中有一个重要的原因就是类组件中的 this 指向问题,需要学习成本,浅浅总结一下

1. 方法一:高阶函数/函数柯里化

直接调用,得到一个函数,点击了执行

import React, { Component } from "react"

export default class App extends Component {
  // constructor是自己的构造函数,不继承实例属性可以不写

  // 实例属性
  state = {
    count: 0,
  }

  // 一、高阶或者柯里化。可以传参
  // 高阶函数(返回函数的函数,或者函数当做参数传递的函数,比如数组的遍历方法)
  // 函数柯里化:通过函数调用继续返回函数,实现多次接受参数最后统一处理的编码形式

  // 原型方法
  handleClick(a) {
    console.log(a) // "xx"
    console.log(this.state.count) //这里面的this指向实例(调用了)

    // 箭头函数没有this。取决于外部环境的this执行
    // 这里由点击时由 react 内部调用 onClick 合成事件,传递 e 事件对象实参
    return e => {
      console.log(e) // 事件对象
      console.log(a) // "xx"
      console.log(this.state.count)
    }
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick("xx")}>点我+1</button>
      </div>
    )
  }
}

2. 方法二:使用箭头函数包裹

点击了之后,先执行外面的箭头函数,再直接调用

import React, { Component } from "react"

export default class App extends Component {
  // constructor是自己的构造函数,不继承实例属性可以不写

  // 实例属性
  state = {
    count: 0,
  }

  // 二、使用箭头函数包裹 可以传参
  // 原型方法
  handleClick(a, e) {
    console.log(a) // "xx"
    console.log(e) // 事件对象
    console.log(this.state.count) //这里面的this
  }
  render() {
    return (
      <div>
        <button
          // 外面用一个箭头函数包起来点击的时候才执行里面的代码里面的 this指向实例this调用所以事件处理函数中的this也指向实例
          onClick={() => {
            this.handleClick("xx")
          }}
        >
          点我+1
        </button>
      </div>
    )
  }
}

// 箭头函数中传一个事件对象,e就是传递的事件对象,点击时由 react 内部传递事件对象参数 onClick(e)
onClick={(e) => { this.handleClick("xx",e) }}

3. 方法三:使用 bind 方法

调用 bind (原函数里面的代码并没有执行),得到一个新函数,点击了直接调用

import React, { Component } from "react"

export default class App extends Component {
  // constructor是自己的构造函数,不继承实例属性可以不写

  // 实例属性
  state = {
    count: 0,
  }

  // 三、使用bind 可以传参
  // 原型方法
  handleClick(a, b, e) {
    // 这里bind函数调用返回一个新函数,a = 1,b = 2,这里e是事件对象,实际内部由bind产生的新函数传递过来的
    console.log(this.state.count, a, b, e) //这里面的this
  }
  render() {
    return (
      <div>
        <button
          onClick={
            // 下面两个this都指向实例
            // 第一个this是实例为了拿到handleClick后面的this是改变handleClick里面的this指向
            this.handleClick.bind(this, 1, 2)
          }
        >
          点我+1
        </button>
      </div>
    )
  }
}

bind函数,改变this指向,并生产一个新函数,bind和新函数都可以传递参数,bind先传,新函数在后面,叠加

function fn(num, num2){
    console.log(this, num, num2)
}
const o = { age : 18 }
const newFn = fn.bind(o, 18)
newFn(19)

// 事件处理函数中的this指向,让他指向组件实例

事件处理函数里的事件对象,是通过bind返回的新函数里面传递过来的(所以bind的新函数也可以传参)

4. 方法四:实例挂载

不直接调用,挂载到实例上(必须使用箭头函数),点击了执行

import React, { Component } from "react"

export default class App extends Component {
  // constructor是自己的构造函数,不继承实例属性可以不写

  // 实例属性
  state = {
    count: 0,
  }

  // 四、实例挂载(平常用的最多,不能传参时使用)

  // 1.多个实例复用,造成稍微的内存浪费
  // 2.无法事件传参,但是可以事件处理返回一个函数来传参,多余了,这样就可以直接用前面三种

  temp = this // 这是实例挂载的意思,这里的this就是指向的实例 a是实例 a.temp === a 为true
  //实例挂载方法

  // 注意箭头函数跟谁调用无关
  handleClick = () => {
    console.log(this.state.count) //这里面的this和外部环境的一样,外部的this是指向实例
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>点我+1</button>
      </div>
    )
  }
}

5. 方法五:实例引用原型方法

需要写 constructor 函数,也要调用 bind 调用得到一个新函数,把原型上的方法引用一份到实例

import React, { Component } from "react"

export default class App extends Component {
  constructor() {
    super()
    this.state = {
      count: 0,
    }
    // 实例方法
    // 根据原型上的 handleClick 生成一个新方法给了实例属性 aaa 并把原型方法 handleClick 内部的 this 改成实例
    this.aaa = this.handleClick.bind(this)
  }

  // 五、实例引用原型的方法 无法传参
  // 也是无法传参,但是多个实例都是引用的同一个原型的方法

  // 原型方法
  handleClick() {
    console.log(this.state.count)
  }
  render() {
    return (
      <div>
        <button onClick={this.aaa}>点我+1</button>
      </div>
    )
  }
}

6. 小结

  • 前三种,事件处理函数都是挂载到原型上,可以传参,传参相当于函数直接调用了,this直接指向了实例(返回函数,外面用箭头函数包裹,bind 返回一个新函数)
  • 后两种,事件处理函数第四种直接挂载到实例上,第五种,是挂载原型上,但是实例中引用了一份(使用bind 改变一下 this) 这两种不能传参

React 中的合成事件不像 Vue 和 原生 JS ,React 中加了括号就会立即执行,如果需要可以在外面套上一个箭头函数,这样并不会立即执行

我自己比较常用的(看个人习惯)

// 传参
// 第二种外面用一个箭头函数包裹,事件处理函数挂载到原型上
handleClick(a, e) {
    console.log(a) // "xx"
    console.log(e) // 事件对象
    console.log(this.state.count) //这里面的this
  }
  render() {
    return (
      <div>
        // 外面用一个箭头函数包起来,点击的时候才执行里面的代码,里面的 this指向实例,this调用,所以事件处理函数中的this也指向实例
        <button onClick={() => { this.handleClick("xx") }}>点我+1</button>
      </div>
    )
  }

  
// 不传参
// 第四种,事件处理函数直接挂载到实例上
temp = this // 这是实例挂载的意思,这里的this就是指向的实例 a是实例 a.temp === a 为true
  //实例挂载方法

  // 注意箭头函数跟谁调用无关
  handleClick = () => {
    console.log(this.state.count) //这里面的this和外部环境的一样,外部的this是指向实例
  }
  render() {
    return (
      <div>
        <button onClick={ this.handleClick }>点我+1</button>
      </div>
    )
  }