带你找出react中,回调函数绑定this最完美的写法!
flytam Lv4

带你找出react中,回调函数绑定this最完美的写法!(文末 有惊喜)

相信每一个人写过react的人都对react组件的的this绑定有或多或少的了解

在我看来,有若干种this写法,我们通过本文,一步步找优缺点,筛选出最完美的react this写法!(有点小激动)

1、远古时代 React.createClass

说实话,在我接触react的时候,这种写法就只在相关文章见到了。React.createClass会自动绑定所有函数的this到组件上

1
2
3
4
5
6
7
8
9
React.createClass({
fn() {
// this 指向组件本身
console.log(this);
},
render() {
return <div onClick={this.fn}></div>;
}
});

react 0.13开始就已经支持class声明组件了。react 16已经废弃了这种写法,这里就不讨论了。直接淘汰

2、错误示范

1
2
3
4
5
6
7
8
class App extends React.Component {
fn() {
console.log(this);
}
render() {
return <div onClick={this.fn}></div>;
}
}

这种写法,最终打印this是指向undefined。原因在于上面的事件绑定函数调用可以看作如下。

1
2
3
// 伪代码
onClick = app.fn;
onClick();

onClick进行调用时,this的上下文是全局,由于是在es module中,全局this指向undefined,所以这个错误示范的事件处理函数中的this不是指向组件本身的

3、利用proposal-class-public-fields直接绑定箭头函数

1
2
3
4
5
6
7
8
class App extends React.Component {
fn = () => {
console.log(this);
};
render() {
return <div onClick={this.fn}></div>;
}
}

目前proposal-class-public-fields仍处于提案阶段,需要借助@babel/plugin-proposal-class-properties这个 babel 插件在浏览器中才能正常工作

经过babel转换,等价于以下的代码

1
2
3
4
5
6
7
8
9
10
11
class App extends React.Component {
constructor(props) {
super(props);
this.fn = () => {
console.log(this);
};
}
render() {
return <div onClick={this.fn}></div>;
}
}

可以看出,32从最大的区别在于,3fn直接绑定在实例的属性上(2是绑定在原型的方法上),并利用箭头函数继承父级this作用域达到了this绑定的效果。

优点:代码十分简洁,不需要手动写bind、也不需要在constructor中进行额外的操作

缺点:很多文章都提到这是一种完美写法,但其实每一个实例在初始化的时候都会新建一个新事件回调函数(因为绑定在实例的属性上,每个实例都有一个fn的方法。本质上,这是一种重复浪费),所以其实并不是很完美

4、Constructor中使用 bind

1
2
3
4
5
6
7
8
9
10
11
12
class App extends React.Component {
constructor(props) {
super(props);
this.fn = this.fn.bind(this);
}
fn() {
console.log(this);
}
render() {
return <div onClick={this.fn}></div>;
}
}

优点:几乎等价于3

缺点:代码写起来比较繁琐,需要在constructor中,手动绑定每一个回调函数

5、在render中进行bind绑定

1
2
3
4
5
6
7
8
class App extends React.Component {
fn() {
console.log(this);
}
render() {
return <div onClick={this.fn.bind(this)}></div>;
}
}

优点:fn函数多次实例化只生成一次,存在类的属性上,类似于4,写法上比4稍微好一点。

缺点:this.fn.bind(this)会导致每次渲染都是一个全新的函数,在使用了组件依赖属性进行比较、pureComponent、函数组件React.memo的时候会失效。

最关键的是5的写法会被6全方面吊打完爆

6、箭头函数内联写法

1
2
3
4
5
6
7
8
class App extends React.Component {
fn() {
console.log(this);
}
render() {
return <div onClick={() => fn()}></div>;
}
}

优点:

1、写法简洁

2、与2-5的写法相比,6写法最大的最大好处就是传参灵活

3、全面吊打写法4,相同的缺点,但是多了传参数灵活。如果需要渲染一个数组,并且数组根据不同项,事件处理不一样时,2-5就很尴尬了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const arr = [1, 2, 3, 4, 5];
class App extends React.Component {
fn(val) {
console.log(val);
}
render() {
return (
<div>
{arr.map(item => (
// 采用 6的写法,要打印数组这一项就很方便
<button onClick={() => this.fn(item)}>{item}</button>
))}
</div>
);
}
}

网上看多文章都在使用3的方案的时候推荐使用闭包传参实现该效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const arr = ["1", "2", "3", "4", "5"];
class App extends React.Component {
fn = val => () => {
console.log(val);
};
render() {
return (
<div>
{arr.map(item => (
// 每次也生成了全新的函数了
<button onClick={this.fn(item)}>{item}</button>
))}
</div>
);
}
}

经过前面的分析。使用这种写法,还不如直接使用6的内联写法,两种每次都是返回全新的函数,而且,少了一次返回闭包函数的开销。

缺点: 每次渲染都是一个全新的函数,类似于5的缺点,在使用了组件依赖属性进行比较、pureComponent、函数组件React.memo的时候会失效

7、函数组件的useCallback

虽然函数组件无this一说法,但既然讲到react回调函数,还是提一下

hook出现之前,函数组件是不能保证每次的回调函数都是同一个的,(虽然可以把回调提到函数作用域外固定,但都是一些 hack 的方法了)

1
2
3
4
const App = () => {
// 每次都是全新的
return <div onClick={() => console.log(2333)}></div>;
};

有了hook。我们便可以使用useCallback固定住回调

1
2
3
4
5
const App = () => {
const fn = useCallback(() => console.log(2333), []);
// 每次都是固定
return <div onClick={fn}></div>;
};

有没有发现。其实很类似class组件的将回调挂在class上,嗯,这就hook强大的地方,利用了react fiber,挂在了它的memorizeState上,实现了能在多次渲染中保持(这就不展开讲了)。缺点还是和上面提过的,参数传递不方便,如渲染数组

8、(最完美)的写法?

当然,如果不使用内联写法又获取到参数行不行呢。当然也是可以的,利用元素的自定义属性data-属性传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const arr = ["1", "2", "3", "4", "5"];
class App extends React.Component {
constructor(props) {
super(props);
this.fn = this.fn.bind(this);
}
fn(e) {
// 1 2 3 4 5
console.log(e.target.dataset.val);
}
render() {
return (
<div>
{arr.map(item => (
// 每次也生成了全新的函数了
<button data-value={item} onClick={this.fn}>
{item}
</button>
))}
</div>
);
}
}

orz! 这是最完美写法了吧!不考虑代码繁琐的情况下,既正确绑定了this,又不会多次实例化函数,又能渲染数组。。

其实还是错误的…data-xxx属性只能传递string类型的数据,因为是附加给html的,react会进行一步JSON.stringify的操作,如果你传递一个对象,打印出来是value: "[object Object]"。果然,就算是为了获取字符串参数,也不推荐这种写法。可以,**但没必要!**

9、最省事的写法?

有一位大佬写了一个 babel 插件babel-plugin-react-scope-binding的插件,能够实现 将2的错误示范自动转化内联函数,更牛逼的是还能传参。介绍。确实是最省事的写法,不过很容易引起歧义,也有上面提到的问题

好吧,感谢你看到这里,废话连篇一篇文章,其实似乎并没有找回完美的写法。。。

下面说说本人的一些愚见吧

在平时写代码中,在render没有非常大的开销情况下(也没有依赖组件的某些属性进行性能优化、没使用 pureComponent), 会优先使用纯内联的写法(无论是函数组件还是 class 组件)。因为重新创建函数开销我觉得不是特别大的,并且内联我觉得还有最大的好处就是,看到一个事件调用,不需要再点到事件函数调用的地方…减少了飞来飞去的情况,而且上面也提到,内联传递参数是非常方便的。在实在遇到性能问题,再考虑优化。无需为了优化而优化

最近春招季,看完这篇文章,虽然还是找不出最完美的react绑定事件写法,但是面试官提起react绑定事件的几种区别时,相信大家都能答出来了。。。。

  • Post title:带你找出react中,回调函数绑定this最完美的写法!
  • Post author:flytam
  • Create time:2020-03-09 20:18:09
  • Post link:https://blog.flytam.vip/带你找出react中,回调函数绑定this最完美的写法!.html
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.