了解记录react 开发解决 this 指向的五种方法

124 阅读4分钟

一、通过案例浅谈 react 开发中 this 指向问题

效果图:

anli.jpg

点击计数案例:

import React, { Component } from 'react'

// 需求:点击按钮让数字在原来的基础上加 1
// 1. 给按钮绑定点击事件
// !2. 在事件回调里面,获取原来的数字
// 3. 加 1
export default class App extends Component {
  state = {
    // 提取到状态里面的 count 可以被改变,改变之后视图会更新
    count: 0,
  }
  handleClick() {
    // 类当中的方法默认就是开启了严格模式
    // 代码打包之后其实也会严格模式
    console.log(this) // undefined,不是实例,如果是实例该多好啊,为什么是 undefined 呢?
    // console.log(this.state.count)
  }
  render() {
    return (
      <div style={{ textAlign: 'center' }}>
        <h2>计数器:{this.state.count}</h2>
        {/* 一定是 this.handleClick */}
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

怎么解决呢? 解释一下上述 this 为什么是 undefined

<script>
    class App {
      handleClick() {
        // 方法中的 this 谁调用就是谁
        console.log(this) // this
      }
    }

    const a = new App()
    // a.handleClick()
    const onClick = a.handleClick
    onClick() // window.onClick()
</script>

首先解决 this 的指向问题

handleClick() {
    // 方法中的 this 谁调用就是谁
    // 期望 this => 实例
    console.log(this)
    return undefined
  }
  render() {
    return (
      <div style={{ textAlign: 'center' }}>
        <h2>计数器:{this.state.count}</h2>
        {/* 一旦加了括号,表示是 this 调用的,为 render 中的 this 就是实例,说明是实例调用的 */}
        {/* 这样确实解决了 this 指向问题,但是又引来一个新问题,就是点击按钮的时候执行的是 undefined */}
        <button onClick={this.handleClick()}>+1</button>
      </div>
    )
  }

#1 解决this指向的第1种方式

import React, { Component } from 'react'

// 需求:点击按钮让数字在原来的基础上加 1
// 1. 给按钮绑定点击事件
// !2. 在事件回调里面,获取原来的数字
// 3. 加 1
export default class App extends Component {
  state = {
    count: 0,
  }
  // 高阶函数:返回函数的函数,或参数是函数的函数,[].map((item) => {})
  // 函数柯里化:通过函数调用继续返回函数的形式,实现多次接收参数最后统一处理的编码形式
  handleClick(num) {
    // this => 实例
    return (e) => {
      // num e => 统一处理
      // 箭头函数没有自己的 this,指向外部,外部的 this 就是实例
      console.log(this.state.count)
    }
  }
  render() {
    return (
      <div style={{ textAlign: 'center' }}>
        <h2>计数器:{this.state.count}</h2>
        {/* handleClick() 调用,返回一个箭头函数 */}
        <button onClick={this.handleClick(1)}>+1</button>
      </div>
    )
  }
}

#2 解决this指向的第2种方式

import React, { Component } from 'react'

// 需求:点击按钮让数字在原来的基础上加 1
// 1. 给按钮绑定点击事件
// !2. 在事件回调里面,获取原来的数字
// 3. 加 1
export default class App extends Component {
  state = {
    count: 0,
  }
  handleClick() {
    // 实例调用的 => this => 实例
    console.log(this.state.count)
  }
  render() {
    return (
      <div style={{ textAlign: 'center' }}>
        <h2>计数器:{this.state.count}</h2>
        <button onClick={(e) => this.handleClick()}>+1</button>
      </div>
    )
  }
}

#3 解决this指向的第3种方式

import React, { Component } from 'react'

// 需求:点击按钮让数字在原来的基础上加 1
// 1. 给按钮绑定点击事件
// !2. 在事件回调里面,获取原来的数字
// 3. 加 1
export default class App extends Component {
  state = {
    count: 0,
  }
  handleClick(a, b, e) {
    // 最后对应不上的那个形参就是事件对象
    console.log(this.state.count, a + b, e)
  }
  render() {
    return (
      <div style={{ textAlign: 'center' }}>
        <h2>计数器:{this.state.count}</h2>
        {/* bind 谁,handleClick 中的 this 就是谁,bind 的 this 是实例,所以 handleClick 中的 this 就是实例 */}
        {/* bind 返回的是一个新函数,所以点击按钮的时候执行的就是这个新函数,而新函数和 handleClick 公用的是一个函数体,所以执行的就是 handleClick 里面的代码 */}
        <button onClick={this.handleClick.bind(this, 1, 2)}>+1</button>
      </div>
    )
  }
}

#4 解决this指向的第4种方式

import React, { Component } from 'react'

// 需求:点击按钮让数字在原来的基础上加 1
// 1. 给按钮绑定点击事件
// !2. 在事件回调里面,获取原来的数字
// 3. 加 1
export default class App extends Component {
  state = {
    count: 0,
  }
  // 实例属性,装的是一个箭头函数
  // 如果说我有办法,证明这儿的 this 是实例,是不是就能说明 handleClick 箭头函数里面的 this 是实例呀~
  handleClick = () => {
    console.log(this.state.count)
  }
  render() {
    return (
      <div style={{ textAlign: 'center' }}>
        <h2>计数器:{this.state.count}</h2>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

证明箭头函数外部的this就是实例

class App {
  // 把 this 赋值给了实例属性 temp
  temp = this // #1
  // 证明这儿的 this 是实例
  handleClick = () => {
    console.log(this)
  }
}

const a = new App()
console.log(a.temp === a) // true,就是说明 #1 处的 this 就是实例

#5 解决this指向的第5种方式

import React, { Component } from 'react'

// 需求:点击按钮让数字在原来的基础上加 1
// 1. 给按钮绑定点击事件
// !2. 在事件回调里面,获取原来的数字
// 3. 加 1
export default class App extends Component {
  constructor() {
    super()
    this.state = {
      count: 0,
    }
    // 1. 右边的 this.handleClick.bind(this),会优先从实例上面找 handleClick,发现实例上面没有 handleClick
    // 2. 所以找到了原型上的 handleClick
    // 3. 然后通过 bind 把原型上的 handleClick 里面的 this 变成了这儿 this(实例)
    // 4. 同时把返回的新函数给了实例上面的 handleClick 属性
    // 5. 此时实例上面的 handleClick 属性对应的新方法和原型上面的 handleClick 公用一个函数体
    // 6. 意味着调用实例上面的 handleClick 的时候也会执行原型上面的 handleClick 函数体
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    console.log(this.state.count)
  }
  render() {
    return (
      <div style={{ textAlign: 'center' }}>
        <h2>计数器:{this.state.count}</h2>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

完成案例点击计数

handleClick = () => {
    // 数据确实变了,但视图不是响应式的
    // this.state.count += 1
    // !注意:setState() 的操作是合并,不会影响没有操作到的数据
    this.setState({
      // key 表示要修改的数据
      // value 表示要变成的结果
      count: this.state.count + 1,
    })
  }