一、React 事件系统特点
-
合成事件(SyntheticEvent)
-
React 封装了原生事件,提供跨浏览器一致的事件接口。
-
事件对象会被复用(需通过
e.persist()
保留引用)。
-
-
驼峰命名法
事件名使用onClick
而非onclick
。 -
事件委托
React 默认在根节点委托事件,通过冒泡机制处理。
二、事件绑定方式
1. JSX 内联绑定
直接在 JSX 中绑定函数(推荐用于简单场景):
function Button() {
const handleClick = () => {
console.log("按钮被点击");
};
return <button onClick={handleClick}>点击我</button>;
}
2. 传递参数
通过箭头函数或 bind
传递参数:
function List() {
const handleItemClick = (id, e) => {
console.log("点击项 ID:", id, "事件对象:", e);
};
return (
<ul>
{[1, 2, 3].map((item) => (
<li
key={item}
onClick={(e) => handleItemClick(item, e)} // 箭头函数传参
>
项目 {item}
</li>
))}
</ul>
);
}
三、事件对象(SyntheticEvent)
React 事件对象提供与原生事件一致的属性和方法,常用 API:
-
e.target
:触发事件的 DOM 元素。 -
e.currentTarget
:绑定事件的 DOM 元素。 -
e.preventDefault()
:阻止默认行为。 -
e.stopPropagation()
:阻止冒泡。
访问原生事件
通过 e.nativeEvent
访问底层原生事件:
function HandleNativeEvent() {
const handleClick = (e) => {
console.log("原生事件类型:", e.nativeEvent.type);
};
return <button onClick={handleClick}>点击查看原生事件</button>;
}
四、常见事件类型与示例
1. 表单事件
function Form() {
const [inputValue, setInputValue] = useState("");
const handleChange = (e) => {
setInputValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("表单提交值:", inputValue);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" value={inputValue} onChange={handleChange} />
<button type="submit">提交</button>
</form>
);
}
2. 鼠标事件
function MouseTracker() {
const handleMouseMove = (e) => {
console.log("鼠标位置:", e.clientX, e.clientY);
};
return <div onMouseMove={handleMouseMove}>移动鼠标查看坐标</div>;
}
3. 键盘事件
function KeyboardInput() {
const handleKeyDown = (e) => {
if (e.key === "Enter") {
console.log("按下了回车键");
}
};
return <input type="text" onKeyDown={handleKeyDown} />;
}
五、性能优化与最佳实践
1. 避免在 render 中创建新函数
错误示例(导致子组件不必要的重新渲染):
// ❌ 每次渲染都会创建新函数
<button onClick={() => console.log("点击")}>按钮</button>
优化方案:
// ✅ 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
console.log("点击");
}, []);
return <button onClick={handleClick}>按钮</button>;
2. 阻止默认行为与冒泡
function PreventDemo() {
const handleLinkClick = (e) => {
e.preventDefault(); // 阻止链接跳转
e.stopPropagation(); // 阻止事件冒泡
console.log("链接点击被阻止");
};
return (
<a href="https://react.dev" onClick={handleLinkClick}>
阻止跳转的链接
</a>
);
}
3. 事件委托优化
利用 e.target
处理动态列表:
function List() {
const handleListClick = (e) => {
if (e.target.tagName === "LI") {
console.log("点击项内容:", e.target.textContent);
}
};
return (
<ul onClick={handleListClick}>
<li>项目 1</li>
<li>项目 2</li>
<li>项目 3</li>
</ul>
);
}
六、常见问题与解决方案
1. 事件处理函数中的 this
为 undefined
-
原因:类组件中未正确绑定
this
。 -
解决:使用箭头函数或在构造函数中绑定。
2. 事件对象属性被重置
-
现象:异步代码中访问
e.target
为null
。 -
解决:调用
e.persist()
保留事件对象:const handleClick = (e) => { e.persist(); // 保留事件对象 setTimeout(() => { console.log("异步访问:", e.target); }, 1000); };
3. React 事件与原生事件混用
-
注意:React 事件通过
document
委托,原生事件可能先于 React 事件触发。 -
建议:优先使用 React 事件系统。
React 常用通信方式
一、父子组件通信
1. 父传子:Props
-
场景:父组件向子组件传递数据。
-
实现:
// 父组件 function Parent() { const data = "Hello from Parent"; return <Child message={data} />; } // 子组件 function Child({ message }) { return <div>{message}</div>; }
2. 子传父:回调函数
-
场景:子组件通知父组件事件或传递数据。
-
实现:
// 父组件 function Parent() { const handleChildEvent = (data) => { console.log("子组件传递的数据:", data); }; return <Child onEvent={handleChildEvent} />; } // 子组件 function Child({ onEvent }) { return <button onClick={() => onEvent("Child Data")}>触发事件</button>; }
二、兄弟组件通信
3. 状态提升(Lifting State Up)
-
场景:兄弟组件共享同一状态,将状态提升到共同父组件。
-
实现:
// 父组件 function Parent() { const [sharedState, setSharedState] = useState(""); return ( <div> <ChildA onUpdate={setSharedState} /> <ChildB state={sharedState} /> </div> ); } // 子组件A(更新状态) function ChildA({ onUpdate }) { return <input onChange={(e) => onUpdate(e.target.value)} />; } // 子组件B(显示状态) function ChildB({ state }) { return <div>当前值: {state}</div>; }
三、跨层级组件通信
4. Context API
-
场景:跨多层组件传递数据,避免逐层传递 Props。
-
实现:
// 1. 创建 Context const ThemeContext = React.createContext("light"); // 2. 父组件提供数据 function App() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } // 3. 深层子组件消费数据 function Toolbar() { return <ThemedButton />; } function ThemedButton() { const theme = useContext(ThemeContext); // 直接获取 Context 值 return <button>当前主题: {theme}</button>; }
四、全局状态管理
5. Redux 或 Zustand
-
场景:复杂应用中多个组件共享全局状态。
-
实现(Redux 示例):
// 1. 定义 Store 和 Reducer const store = createStore((state = { count: 0 }, action) => { switch (action.type) { case "INCREMENT": return { count: state.count + 1 }; default: return state; } }); // 2. 父组件包裹 Provider function App() { return ( <Provider store={store}> <Counter /> </Provider> ); } // 3. 子组件连接 Store function Counter() { const count = useSelector((state) => state.count); const dispatch = useDispatch(); return ( <div> <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button> <p>Count: {count}</p> </div> ); }
五、非父子组件通信
6. Event Bus / 发布订阅模式
-
场景:任意组件间通信(慎用,可能破坏 React 数据流)。
-
实现:
// 1. 创建事件总线 const eventBus = new EventEmitter(); // 2. 组件A发布事件 function ComponentA() { return ( <button onClick={() => eventBus.emit("event", "Data from A")}> 发送事件 </button> ); } // 3. 组件B订阅事件 function ComponentB() { const [data, setData] = useState(""); useEffect(() => { eventBus.on("event", setData); return () => eventBus.off("event", setData); // 清理订阅 }, []); return <div>接收数据: {data}</div>; }
六、其他通信方式
7. Ref 传递(forwardRef + useImperativeHandle)
-
场景:父组件直接调用子组件方法或操作 DOM。
-
实现:
// 子组件 const Child = forwardRef((props, ref) => { useImperativeHandle(ref, () => ({ childMethod: () => { console.log("子组件方法被调用"); }, })); return <div>子组件</div>; }); // 父组件 function Parent() { const childRef = useRef(); return ( <div> <Child ref={childRef} /> <button onClick={() => childRef.current.childMethod()}> 调用子组件方法 </button> </div> ); }
七、通信方式对比与选择
方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
Props | 父子组件简单数据传递 | 简单直观 | 深层嵌套时需逐层传递 |
Context API | 跨层级组件共享主题、用户信息等 | 避免 Props 逐层传递 | 不适合频繁更新的数据 |
Redux/Zustand | 复杂全局状态管理 | 状态集中管理,易于调试 | 增加项目复杂度 |
Event Bus | 非父子组件解耦通信 | 高度灵活 | 破坏 React 单向数据流 |
Ref 传递 | 父组件操作子组件 DOM 或方法 | 直接访问子组件实例 | 违反组件封装原则,慎用 |
八、最佳实践
-
优先使用 Props 和 Context:保持数据流清晰。
-
避免过度使用 Redux:仅在全局状态复杂时引入。
-
慎用 Event Bus:容易导致代码难以维护。
-
优化性能:结合
React.memo
、useMemo
、useCallback
减少不必要的渲染。