前言
大家好,这篇文章是React系列的第二篇,之前已经介绍过React的一些基础的东西,包括如何初始化、组件式开发、useState。今天用这篇文章接着介绍,React的基础之一---事件触发机制!我们通过一个制作一个音乐播放的按钮,先讲解原生方式的实现,再进行抛砖引玉,详细地介绍React的实现,以及React的一些重要知识点!
原生方式播放音乐
我们的目的是为了实现一个小按钮,在点击后,能够在网页中播放音乐,当然,用户初始进入到网页时,不会自动播放音乐,这里注意我们必须得注重用户体验,咱做前端的,切记要极致优化用户体验,如果你创建一个网页,用户一点击该网页就自动播放音乐的话,可能会给用户带来"惊吓",所以我们需要做一个按钮,只有在点击后才能播放音乐!
原生方式实现
首先我们创建一个文件夹,名为audio,然后在这个文件夹下创建我们具体实现效果的页面,以及创建一个sounds文件夹,用于存放我们将来要播放的音乐文件snare.wav
引入audio标签
首先我们要介绍播放音乐的标签是<audio>
,我们可以在这个标签中设置音乐文件,实现播放对应的音乐。
我们为audio标签指定src属性,即指定具体的音乐文件,这里将src设置为sounds里面的snare.wav。
html
<audio src="./sounds/snare.wav"></audio> //将audio标签放入body中
此时我们打开页面,发现啥也没看着,这是因为audio这个标签是专门用来播放音乐的,不会在页面上显示,默认也不会播放音乐,这是我们前面提到的,为了用户体验,不会自动播放,也不会显示出来。
但是我们可以打开浏览器的检查,在网络中看到它。
引入button标签
既然audio不能在页面中显示,那么用户如何播放音乐呢?这时候就要写一个button标签,名为播放。专门让用户实现交互的效果。
html
<button id="play">播放</button>
接下来我们就要真正的实现效果:用户在点击button后,网页能够播放audio指定的音乐。我们使用原生DOM编程,编写一段JavaScript代码实现交互:
js
// 获取页面中的audio元素
const oAudio = document.querySelector('audio');
// 为播放按钮添加点击事件监听器
document.querySelector('#play').addEventListener('click', () => {
oAudio.play(); //调用audio元素的play方法
})
- 我们通过
document.querySelector('audio')
获取页面中的第一个audio元素,并将其存储在oAudio
变量中。 - 接着使用
document.querySelector('#play')
找到ID为'play'的按钮元素,并为其添加点击事件监听器。 - 当按钮被点击时,会执行回调函数,调用
oAudio.play()
方法来播放音频。 - 这样当用户点击按钮时,就能听到audio元素指定的音频内容了。
完整代码展示:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio 音乐</title>
</head>
<body>
<audio src="./sounds/snare.wav"></audio>
<button id="play">播放</button>
<script>
const aAudio = document.querySelector('audio');
document.querySelector('#play').addEventListener('click', () => {
aAudio.play();
})
</script>
</body>
</html>
原生实现的特点
上面的效果是用原生DOM编程实现的,它有以下特点:
- 我们在选择元素时必须要使用document的一系列API
- HTML部分和JS部分是分离的,各自做专门的事情:HTML负责页面结构,JS负责交互
- 需要显示绑定事件监听器,事件的实现较为繁琐
DOM1 与 DOM2
其实我们的DOM编程有DOM1和DOM2两种,上面介绍的是DOM2,下面来给大家介绍一下DOM1编程实现上面的效果
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio 音乐 (DOM1实现)</title>
</head>
<body>
<!-- DOM1 通常依赖元素的 name 或 id 直接访问 -->
<audio id="myAudio" src="./sounds/snare.wav"></audio>
<!-- 内联事件绑定是DOM1的典型特征 -->
<button id="play" onclick="playAudio()">播放</button>
<script>
function playAudio() {
const audio = document.getElementById('myAudio');
audio.play(); // 播放逻辑不变
}
</script>
</body>
</html>
你会发现,DOM1的编程是没有事件监听addEventListener
,它采用的是内联事件(如直接在HTML标签上写onclick等),它的HTML和JS高度耦合。其实这会带来一些问题:
- 扩展性差:难以动态绑定/解绑事件
- 全局污染:playAudio()必须暴露在全局作用域
所以现代原生编程使用的是上文的DOM2编程,它增强了DOM操作能力,引入了更强大的API。
不过尽管DOM2比DOM1更强大,但仍然存在一些问题:
- 手动DOM操作繁琐 :每次更新UI都需要手动选择元素并修改(如
innerHTML
、appendChild
)。 - 性能问题:频繁的DOM操作会导致浏览器重绘/回流(Reflow & Repaint),影响性能。
- 状态管理困难:数据变化时,需要手动同步DOM,容易出错。
所以下面介绍的React采用了声明式UI和虚拟DOM解决了这些问题。下面让我们详细介绍React的方式实现!
React方式播放音乐
接下来,介绍用React的方式实现,我们首先初始化一个React项目,名为react-music。如下是项目的结构。
由于逻辑较为简单,下面我们就不用创建子组件,直接在App里写我们的页面了。
创建audio
首先,我们用jsx语法,在App函数的返回值中,先创建一个元素,这是我们待会要实现的音乐元素。同样的指定src为/sounds/snare.wav
。
js
return (
<>
<audio src="/sounds/snare.wav"></audio>
</>
)
创建button
接着,我们继续创建一个button元素,用于用户交互,是用户实现播放的按钮。
jsx
return (
<>
<audio src="/sounds/snare.wav"></audio>
<button >播放</button>
</>
添加事件
接着,我们准备为button按钮添加事件
回想DOM2编程,我们使用DOM的原生API document....
实现元素的选择,然后使用事件监听器实现事件的实现。但是React并不是用这种方式,它会直接在该标签上指定事件和触发时的回调函数。
让我们为button添加点击事件的触发的函数,并且声明这个函数的执行内容。
html
......
const playMusic = () => {
// 函数触发时执行的内容。
}
return (
<>
<audio src="/sounds/snare.wav"></audio>
<button onClick={playMusic}>播放</button>
</>
)
......
通过观察,你可能会发现,这种方式和DOM1的方式很类似,都是直接在元素上指定事件,但它们只是形式相似,实则React实现这个很复杂,底层大有天地。
useRef
我们现在需要在playMusic实现具体的内容,选择audio标签并且执行play()方法。那么问题来了?如何选择audio标签呢?
我们将这个任务交给useRef,在React中,通过useRef选择DOM节点,实现操作DOM.
我们首先创建一个audioPlayer
的ref 初始设置为null
const audioPlayer = useRef(null)
。
接着我们绑定该ref到audio标签:
<audio ref={audioPlayer} ...>
。
实现播放效果
接着我们书写playMusic,实现真正的播放效果!
js
const playMusic = () => {
audioPlayer.current.play();
}
在用户点击按钮button之后,按钮会调用audioPlayer当前绑定的audio的play方法,此时就大功告成了!你会发现相对来讲整个过程比DOM1和DOM2都简单多了!
下面给出源代码
js
import { useState , useRef } from 'react'
import './App.css'
function App() {
const audioPlayer = useRef(null);
const playMusic = () => {
audioPlayer.current.play();
}
return (
<>
<audio ref={audioPlayer} src="/sounds/snare.wav"></audio>
<button onClick={playMusic}>播放</button>
</>
)
}
export default App
React方式特点
通过这个音乐播放器的实现,我们可以总结出 React 事件处理的几个关键特点:
-
声明式事件绑定
- 不同于 DOM2 的
addEventListener
,React 直接在 JSX 中通过类似onClick={handler}
的方式绑定事件 - 更直观,与 UI 结构紧密结合
- 不同于 DOM2 的
-
合成事件系统
- React 使用自己的合成事件(SyntheticEvent)系统,不是原生 DOM 事件
- 底层实现了事件委托,将所有事件委托到 document 级别处理
- 提供了跨浏览器的一致事件接口
-
Ref 机制访问 DOM
- 使用
useRef
创建引用对象 - 通过
ref
属性绑定到 DOM 节点 - 通过
ref.current
访问实际 DOM 节点 - 比直接使用
document.querySelector
更符合 React 理念
- 使用
-
函数式组件中的实现
- 事件处理函数就是普通的 JavaScript 函数
- 可以方便地访问组件内的状态和 props
- 不需要考虑 this 绑定问题(相比类组件)
-
与原生 DOM 操作对比的优势
- 更简洁:不需要手动选择元素和添加/移除事件监听器
- 更安全:自动处理事件解绑,减少内存泄漏风险
- 更高效:利用 React 的虚拟 DOM 和协调算法优化性能
-
单向数据流体现
- 通过事件触发状态变化
- 状态变化驱动 UI 更新
- 不直接操作 DOM,而是通过状态管理 UI
这种模式体现了 React 的核心思想:开发者只需声明"UI 应该是什么样子",而不需要关心"如何更新到那个样子"。React 会高效地处理 DOM 操作和事件管理,让开发者可以更专注于业务逻辑。