在 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>
)
}