浅谈React事件系统-事件绑定

浅谈React事件系统主要分为两篇:

浅谈React事件系统-事件绑定
浅谈React事件系统-源码解读

场景

一位同事在群里抛出一个问题, 假设一个列表有800个节点, 给每个节点都绑定一个 onClick事件, 如果下面这样子写页面就会有非常卡

1
2
3
4
5
6
7
8
9
10
<ul>
{
list.map((item) => {
return (<li
value={item.value}
onClick={(event) => {onClick(event, item.desc)}
></li>)
})
}
</ul>

假如当这个list的长度为800个节点的时候, 每次渲染都会进行都会由于生成一个新的匿名函数 (event) => {onClick(event, data) , props.onClick的已用发生改变, 导致li这个组件会重新执行render, 如果是 800个节点, 自然就卡了.

要解决不卡顿, 就要保证每次传入的onClick引用不发生变化

先声明事件处理函数

不推荐在大量节点的场景使用下面的绑定事件方式

1
2
onClick={this.handleClick.bind(this)} 
onClick={(event) => {onClick(event, data)}

这种方式, 因为bind本身就是产生一个新的函数. 这两种的onClick引用会发生变化, 在使用PureComponent的时候, 容易触发重复的渲染

推荐在类里面先声明好事件处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class App extends PureComponent {
constructor(props) {
super(props)
}
// 箭头函数自动绑定this作用域
// 这种类实例的公共属性方法 在 ES2018 stage-3
_onClick = (e) => {
console.log(e)
}
// 构造器声明也会在初始化的时候绑定this作用域
_onClick(e) {
console.log(e)
}
render() {
return (
<button onClick={this._onClick}></button>
)
}
}

解决参数传递

  1. 直接给组件传入已经定义好的方法, 我们先在组件里面定义个onClick方法, 当然这样子也会有问题, 假如我要传入相关的数据咋办, 比如我要传一个desc的字符串到 onClick方法里面, 怎么实现, 这里我们可以通过data-xxx的属性来实现, 在onClick中只需要 event.tcurrentTarget.dataset.desc就可以获取到 desc字符串了.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class App extends PureComponent {
const _onClick = (event) => {
const desc = event.currentTarget.dataset.desc
// other code.....
}
render() {
return (
<ul>
{
list.map((item) => {
return (<li
value={item.value}
data-desc={item.desc}
onClick={this._onClick}
></li>)
})
}
</ul>)
}
}
  1. 把list 转换为 listMap的结构, 每次只需要传入索引值, 在onClick方法里面从 listMap 中取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class App extends PureComponent {
constructor(props) {
super(props)
this.listMap = list.reducer((pre, item) => {
pre[item.value] = item.desc
return pre
}, {})
}
const _onClick = (event) => {
const desc = this.listMap[event.curentTarget.value]
// other code.....
}
render() {
return (
<ul>
{
list.map((item) => {
return (<li
value={item.value}
data-desc={item.desc}
onClick={this._onClick}
></li>)
})
}
</ul>)
}
}

下文引言

在完成重构Tree组件的过程中, 为了追求最好的性能, 我也对代码进行如上的改造, 这一改就出问题了

每个人都有这样子一种经历吧, 因为某些意外的原因看到一些特殊的现象, 就以为自己发现新大陆了.

在改造代码过程中, 发现每次onClick事件中event对象中的currentTarget都是指向 document, 由于自己接触过一点react的事件模型, React的所有的事件都是绑定到 document上面, 并不是绑定到元素上面的. 所以看整个dom树只有document是绑定了事件的. 然后再由React事件系统去分发事件, 这样子一联想, 可不得了, 我就以为自己发现React事件模型的bug了, 作为这么成熟的前端框架, 居然currentTarget这个属性都有问题了,这明显是不合理的….. 我把我的发现分享到群里, 结果脸好疼…..

痛定思过, 重新写demo来排查问题, 结果是自己的代码逻辑错误, 同时也研究了一下React Event源码