React 事件绑定的方式有哪些?区别?
问题解析(面试官考察点)
面试官通过此问题主要考察:
- 对 React 类组件 this 绑定问题的理解
- 不同绑定方式的优缺点
- 性能优化的意识
- 对现代 React(Hooks)的掌握
核心概念(基础知识点)
为什么需要绑定 this
在 JavaScript 中,类的方法默认不会绑定 this。当方法作为回调函数传递时,this 会丢失:
class ShowAlert extends React.Component {
showAlert() {
console.log("Hi");
console.log(this); // undefined!
}
render() {
return <button onClick={this.showAlert}>show</button>;
}
}
四种绑定方式
- render 方法中使用 bind
- render 方法中使用箭头函数
- constructor 中 bind
- 定义阶段使用箭头函数绑定
详细解答(代码示例)
方式一:render 方法中使用 bind
class App extends React.Component {
handleClick() {
console.log('this > ', this);
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>test</div>
);
}
}
缺点: 每次 render 都会创建新的函数,影响性能。
方式二:render 方法中使用箭头函数
class App extends React.Component {
handleClick() {
console.log('this > ', this);
}
render() {
return (
<div onClick={(e) => this.handleClick(e)}>test</div>
);
}
}
缺点: 同样每次 render 都会生成新的方法,影响性能。
方式三:constructor 中 bind
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('this > ', this);
}
render() {
return (
<div onClick={this.handleClick}>test</div>
);
}
}
优点: 只在组件实例化时绑定一次,性能较好。
缺点: 编写过于冗长,每个方法都需要在 constructor 中绑定。
方式四:定义阶段使用箭头函数绑定(推荐)
class App extends React.Component {
constructor(props) {
super(props);
}
handleClick = () => {
console.log('this > ', this);
}
render() {
return (
<div onClick={this.handleClick}>test</div>
);
}
}
优点:
- 避免在 render 中重复绑定
- 代码简洁
- 性能最优
深入理解(原理剖析)
this 绑定原理
// 普通函数调用
function normal() {
console.log(this); // undefined(严格模式)或 window
}
// 箭头函数
const arrow = () => {
console.log(this); // 定义时的 this
};
// 类中的方法
class MyClass {
method() {
console.log(this); // 取决于调用方式
}
}
const obj = new MyClass();
obj.method(); // this = obj
const fn = obj.method;
fn(); // this = undefined
四种方式对比
| 方式 | 编写复杂度 | 性能 | 是否推荐 |
|---|---|---|---|
| render 中 bind | 简单 | 差(每次创建新函数) | 不推荐 |
| render 中箭头函数 | 简单 | 差(每次创建新函数) | 不推荐 |
| constructor 中 bind | 复杂 | 好 | 可用 |
| 类属性箭头函数 | 简单 | 好 | 推荐 |
性能影响分析
方式一和方式二的问题:
// 每次 render 都创建新函数
render() {
return (
<ChildComponent
onClick={this.handleClick.bind(this)} // 新函数
/>
);
}
// 导致 ChildComponent 每次都接收新的 prop
// 触发不必要的重新渲染
方式三和方式四的优势:
// 只在实例化时创建一次
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this); // 只执行一次
}
render() {
return (
<ChildComponent
onClick={this.handleClick} // 同一个函数引用
/>
);
}
传递参数的处理
// 方式一:箭头函数(每次创建新函数)
<button onClick={(e) => this.handleClick(id, e)}>Click</button>
// 方式二:data 属性(推荐)
<button data-id={id} onClick={this.handleClick}>Click</button>
handleClick = (e) => {
const id = e.target.dataset.id;
// 处理点击
};
// 方式三:高阶函数
handleClick = (id) => {
return (e) => {
console.log(id);
};
};
<button onClick={this.handleClick(id)}>Click</button>
最佳实践
推荐使用类属性箭头函数
class MyComponent extends React.Component {
state = {
count: 0
};
// 类属性箭头函数自动绑定 this
handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
};
handleDecrement = () => {
this.setState({ count: this.state.count - 1 });
};
handleChange = (e) => {
this.setState({ value: e.target.value });
};
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.handleIncrement}>+</button>
<button onClick={this.handleDecrement}>-</button>
<input onChange={this.handleChange} />
</div>
);
}
}
使用函数组件(最推荐)
import React, { useState, useCallback } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
// useCallback 缓存函数引用
const handleIncrement = useCallback(() => {
setCount(c => c + 1);
}, []);
const handleDecrement = useCallback(() => {
setCount(c => c - 1);
}, []);
return (
<div>
<p>{count}</p>
<button onClick={handleIncrement}>+</button>
<button onClick={handleDecrement}>-</button>
</div>
);
}
事件处理函数命名规范
// 事件处理器命名:handle + 事件名
handleClick
handleSubmit
handleChange
handleMouseEnter
// 传递回调属性命名:on + 动作
onClick
onSubmit
onChange
onUserSelect
避免内联函数定义
// 不推荐:每次 render 创建新函数
render() {
return (
<button onClick={() => this.handleClick()}>
Click
</button>
);
}
// 推荐:使用预定义的函数
render() {
return (
<button onClick={this.handleClick}>
Click
</button>
);
}
处理多个相似事件
class Form extends React.Component {
state = {
username: '',
email: '',
password: ''
};
// 统一处理函数
handleChange = (field) => (e) => {
this.setState({ [field]: e.target.value });
};
render() {
return (
<form>
<input
value={this.state.username}
onChange={this.handleChange('username')}
/>
<input
value={this.state.email}
onChange={this.handleChange('email')}
/>
<input
value={this.state.password}
onChange={this.handleChange('password')}
/>
</form>
);
}
}
面试要点
-
为什么需要绑定 this
- JavaScript 类方法默认不绑定 this
- 作为回调传递时 this 会丢失
-
四种绑定方式的对比
- render 中 bind/箭头函数:简单但性能差
- constructor 中 bind:性能好但代码冗长
- 类属性箭头函数:性能好且代码简洁(推荐)
-
性能考虑
- 避免在 render 中创建新函数
- 使用 React.memo 配合稳定的函数引用
- 函数组件中使用 useCallback
-
现代 React 建议
- 优先使用函数组件
- 类组件中使用类属性箭头函数
- 使用 Hooks 管理状态和副作用
-
常见面试题
- React 中为什么要绑定 this?
- 不同绑定方式的区别?
- 哪种绑定方式性能最好?
- 函数组件如何避免重复创建函数?
-
注意事项
- 箭头函数不会绑定自己的 this
- bind 会创建新函数,影响 PureComponent
- 事件处理函数尽量使用 useCallback 缓存