React 事件处理

参考:

事件处理(React 文档)

从 React 绑定 this,看 JS 语言发展和框架设计

React 事件处理

事件包含了许多细节,在实际开发中容易忽略,造成不是我们期望的结果,本文包含了这部分的细节,解决开发中遇到的 React 事件的问题。

对比传统 HTML

React 元素的事件处理和 DOM 元素的很相似。但是有一点语法上的不同:

  • React 事件绑定属性的命名采用驼峰式写法,而不是小写。
  • 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串( DOM 元素的写法)

传统的 HTML

1
<button onclick="activateLasers()">Activate Lasers</button>

React 中稍稍有点不同:

1
<button onClick={activateLasers}>Activate Lasers</button>

阻止事件默认行为

不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault

传统 HTML 中可以这样写

1
2
3
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>

React 中应该这样写

1
2
3
4
5
6
7
8
9
10
11
12
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log("The link was clicked.");
}

return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}

这里的参数 e 是合成事件,React 根据 W3C spec 来定义这些合成事件,所以你不需要担心跨浏览器的兼容性问题。

在 ES6 中的事件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };

// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}

render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "ON" : "OFF"}
</button>
);
}
}

你必须谨慎对待 JSX 回调函数中的 this,类的方法默认是不会绑定 this 的。如果你忘记绑定 this.handleClick 并把它传入 onClick, 当你调用这个函数的时候 this 的值会是 undefined。

这并不是 React 的特殊行为;它是函数如何在 JavaScript 中运行的一部分。通常情况下,如果你没有在方法后面添加 () ,例如 onClick={this.handleClick},你应该为这个方法绑定 this。

绑定 this

React.createClass 自动绑定

React.createClass 创建的组件,可以自动绑定 this。也就是说,this 这个关键字会自动绑定在组件实例上面。

1
2
3
// This magically works with React.createClass
// because `this` is bound for you.
onChange = {this.handleChange}

当然很遗憾,对于组件的创建,官方已经推荐使用 class 声明组件或使用 functional 无状态组件

渲染时绑定

1
onChange = {this.handleChange.bind(this)}

有一个潜在的性能问题: 当组件每次重新渲染时,都会有一个新的函数被创建。但在正常开发场景中,可以忽略。

箭头函数绑定

1
onChange = {e => this.handleChange(e)}

同样存在潜在的性能问题

Constructor 内绑定

1
2
3
4
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}

重复而累赘

Class 属性中使用 = 和箭头函数

1
2
3
4
handleChange = () => {
// call this function from render
// and this.whatever in here works fine.
};

这种方式的优点:

  • 使用箭头函数,有效绑定了 this
  • 没有第二种方法和第三种方法的潜在性能问题;
  • 避免了方法四的组件实例重复问题;
  • 我们可以直接从 ES5 createClass 重构得来。

这个方法依赖于 ES next 的新特性, 请参考:tc39.github.io > http://babeljs.io/blog/2015/06/07/react-on-es6-plus/ > http://babeljs.io/blog/2015/10/31/setting-up-babel-6/

好消息是在 create-react-app 等工具中已经默认开启。

向事件处理程序传递参数

1
2
3
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>

<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上述两种方式是等价的,分别通过 arrow functionsFunction.prototype.bind 来为特定事件类型添加事件处理程序。

上面两个例子中,参数 e 作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

值得注意的是,通过 bind 方式向监听函数传参,在类组件中定义的监听函数,事件对象 e 要排在所传递参数的后面,例如:

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 Popper extends React.Component {
constructor() {
super();
this.state = { name: "Hello world!" };
}

preventPop(name, e) {
//事件对象e要放在最后
e.preventDefault();
alert(name);
}

render() {
return (
<div>
<p>hello</p>
{/* Pass params via bind() method. */}
<a
href="https://reactjs.org"
onClick={this.preventPop.bind(this, this.state.name)}
>
Click
</a>
</div>
);
}
}

其他问题

事件自动执行了?

例如:

1
2
3
<a href="#" onClick={this.onLoadQR(text)}>
<Icon type="qrcode" />下载
</a>

这种写法是直接执行了函数,所以每一次渲染都会执行

而正确的行为是,我们应该在这里进行事件绑定,因为 React 不会进行自动绑定,具体方法看前文 “绑定 this

不写 this 为什么找不到方法?

例如:

1
<Switch defaultChecked onChange={onChangeSwitch(text)} />

省略了 this, 此时方法虽然写在了 class 内,但是缺无法获取到,此时如果将方法写到 class 外,作为一个全局变量,但这个方法内部的 this 指向缺为 undefined