React 文档
导读与全书目录表:见同目录 01-React 入门:简介·特性与设计范式.md 开头。
React 的事件机制
回答
shell
React 并非直接使用浏览器原生事件,而是基于原生事件封装了一套合成事件(SyntheticEvent) 系统,目的是跨浏览器兼容、统一事件行为,同时优化性能。
React 事件的核心是合成事件(SyntheticEvent) + 事件委托,委托目标在 React 17 后从 document 改为组件根节点 如#root;
React 合成事件封装了原生事件,提供跨浏览器兼容。**React 17 起已移除事件池**,合成事件对象不再被复用;**React 16 及以前**在异步回调里访问事件字段需 **`e.persist()`**,否则可能读到被清空的对象。
事件执行顺序:原生捕获 → React 合成捕获 → React 合成冒泡 → 原生冒泡,手动绑定的原生事件需在组件卸载时解绑。
# 原生与合成事件对比
特性 原生 DOM 事件 React 合成事件
绑定方式 element.addEventListener() JSX 中直接写 onClick={handler}
事件对象 浏览器原生 Event 对象 React 封装的 SyntheticEvent 对象
事件委托目标 绑定在具体元素上 统一委托到 document(React 17 后改为组件根节点)
跨浏览器兼容性 需要手动处理(如 IE 的 attachEvent) 内置兼容,无需关注浏览器差异合成事件的使用与注意点
js
import React from 'react';
function Button() {
const handleClick = (e) => {
console.log(e.target);
e.preventDefault();
e.stopPropagation();
// React 17+:无事件池,异步里仍可读取 e(若需长期保存,请自行拷贝字段或存 nativeEvent)
setTimeout(() => {
console.log(e.target);
}, 1000);
};
return <button onClick={handleClick}>点击我</button>;
}
export default Button;绑定事件&获取 this
在函数组件中
函数组件中无 this,回调函数的默认参数作为 $event
- 方式一:绑定有参数的事件(类属性箭头函数)
js
// 虽然每次渲染都会创建新的函数实例,但是因为需要传参,所以在所难免
const handleClick = (count, $event) => {
setCount((prevCount) => prevCount + count);
};
<div onClick={(e) => handleClick(100, e)}>点击</div>;- 方式二:绑定无参数的事件(函数引用)
js
// 优点是每次渲染都不会创建新的函数实例;无业务参数时 handler 无需形参
const handleClick = () => {
setCount((prevCount) => prevCount + 1);
};
<div onClick={handleClick}>点击</div>;- 方式三:事件作为 props 传递给子组件(useCallback 优化)
js
// 通过依赖项缓存函数结果
const handleClick3 = useCallback((count, $event) => {
setCount((prevCount) => prevCount + count);
}, []);
<Button onClick={(e) => handleClick3(1000, e)}>使用 useCallback 优化性能 ++ </Button>;在类组件中
- 方式一:绑定有参数的事件(类属性箭头函数)
js
// 缺点是每次 render 创建新箭头函数的问题
class MyComponent extends React.Component {
handleClick = (id, $event) => {
console.log('点击了', id);
console.log('this', this); // 类属性箭头函数,自动绑定 this
console.log('$event', $event);
};
render() {
return <button onClick={(e) => this.handleClick(123, e)}>点击按钮</button>;
}
}- 方式二:绑定无参数的事件(构造函数中绑定)
js
// 只绑定一次,性能最优
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('点击事件触发', this); // this 指向当前组件实例
}
render() {
return <button onClick={this.handleClick}>点击我</button>;
}
}- 方式三:事件作为 props 传递给子组件(无参使用方式二,有参数使用高阶函数 + 实例方法的方式)
js
class Parent extends React.Component {
// 1. 定义接收参数的实例方法
handleItemClick = (id) => {
console.log('点击的 item id:', id);
};
// 2. 定义返回函数的高阶函数(仅创建一次)
getHandleClick = (id) => {
return () => this.handleItemClick(id);
};
render() {
return (
<div>
{/* 3. 传递高阶函数的返回值,引用唯一 */}
<Child onClick={this.getHandleClick(1)} />
<Child onClick={this.getHandleClick(2)} />
</div>
);
}
}总结
非复杂场景下的绑定事件的方式优先使用:类属性箭头函数的方式,此法只是每次渲染会重新箭头函数,影响不大
react 表单控件
控件理解
- 受控组件:表单元素的值由 React 控制,通过 value 绑定状态,通过 onChange 事件更新状态
- 非受控组件:表单元素的值由 DOM 控制,通过 ref 获取表单控件的 DOM 元素,从而获取表单中的值,无法控制更新状态
如何选择
- 受控组件(精细化):实时验证场景,实时交互场景
- 非受控组件(简洁化,性能好):直接获取表单值并提交的简单场景
受控组件
- 表单数据由 React 组件管理
- 表单元素的值通过 value 或 checked 属性绑定到 state
- 通过 onChange 等事件处理函数更新 state
- 特点:完全控制表单数据、即时验证和反馈、有条件地禁用/启用按钮、强制输入格式
js
class ControlledForm extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
handleSubmit = (event) => {
alert('提交的值: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
</label>
<button type="submit">提交</button>
</form>
);
}
}非受控组件
- 非受控组件是指表单元素的值由 DOM 自身管理,而不是由 React 控制。
- 特点:表单数据由 DOM 节点自身处理、使用 ref 来从 DOM 获取表单值
js
class UncontrolledForm extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (event) => {
alert('提交的值: ' + this.inputRef.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input
type="text"
defaultValue="默认值"
ref={this.inputRef}
/>
</label>
<button type="submit">提交</button>
</form>
);
}
}注意事项
shell
文件输入 (<input type="file" />) 始终是非受控组件,因为它的值只能由用户设置
在 React 中,value 属性通常用于受控组件,而 defaultValue 用于非受控组件
对于复选框和单选按钮,受控组件使用 checked 属性,非受控组件使用 defaultChecked受控组件和非受控组件的区别?
回答
shell
核心区别:数据由谁控制;
① 受控组件:表单数据由 React 状态(useState)控制,输入 onChange 时更新状态,value 绑定状态;比如 <input value={val} onChange={(e) => setVal (e.target.value)} />;
② 非受控组件:表单数据由 DOM 自身控制,用 useRef 获取值,比如 <input ref={inputRef} />;
场景:需要实时验证、联动的表单用受控;简单表单(比如文件上传)用非受控。