在本文中,我们将学习如何使用React 16中的一些新功能来创建音乐播放器。
在实现此音乐播放器时,我们将了解React 16中的一些更改。有很多更改,因此我们不会涵盖所有更改,但我们将介绍那些重要的内容以及您可以立即实施。
这篇文章的完整资源可以在GitHub上找到 。
要启动该应用程序,请下载代码,将cd
插入项目目录并键入:
npm install
npm start
在React应用程序中状态
所有React应用程序都包含一个名为state
的属性,该属性确定应如何以及应显示哪些组件(以及与这些组件关联的任何数据)。
我们的音乐播放器具有一个state
属性,其中包含两个重要信息:一个变量,该变量指定播放器是否在播放音乐( playing
布尔值);另一个变量,用于跟踪当前曲目的状态,即currentTrackIndex
变量。
this.state = {
playing: false,
currentTrackIndex: 0
};
什么是国家?
当我们提到一个组件的状态时 ,我们指的是页面上该组件实例的快照。
React的组件可以定义自己的状态,我们将在本文中使用。 当我们在React组件中使用state时,该组件被称为有状态的 。 React组件可以使用state
属性定义自己的状态,以处理有状态的组件,例如我们的音乐播放器。
当用户单击播放 , 暂停 , 下一个和上一个按钮以及播放器中的曲目时,我们的组件将更新其当前状态。
道具与状态
对于React应用程序,了解prop和state之间的区别很重要。 我们的音乐播放器有两个state
变量,这些变量确定在给定时间点显示应用程序的方式。 App
组件是驱动子组件显示的主要组件,即Controls
组件和TrackList
组件。 为了使这两个组件能够接收有关我们应用程序状态的信息, App
组件会将信息作为道具传递给子组件。 这些道具然后可以在子组件中使用,以正确显示其应用程序片段。 要了解的另一件重要事情是,每次我们的App
组件更新时,我们的Controls
组件和TrackList
组件也会被更新,因为它们依赖于App
组件中的信息。
控制项
我们的Controls
组件是我们App
组件的第一个子组件。 Controls
组件具有两个道具: onClick
和playing
。 onClick
属性允许我们将在App
组件中定义的handleClick
函数传递给Controls
组件。 当用户单击“ Controls
组件中的按钮之一时,将调用handleClick
函数。 playing
道具可以使Controls
组件知道播放器的当前状态,因此我们可以正确渲染播放图标或暂停图标。
让我们探讨如何在Controls
组件中呈现按钮和处理单击。
在Controls
组件中,我们有三个重要的按钮:
- << (上一个)按钮(指向左侧的箭头图标),用于选择列表中的上一个曲目
- 播放/暂停按钮可播放和暂停音乐
- >> (下一个)按钮-指向右侧的箭头图标-用于选择列表中的下一首曲目。
当单击这些按钮中的每个按钮时,我们将调用从App
组件传入的单击处理程序函数。 音乐播放器应用程序中的每个按钮都有一个id
,该id
将有助于我们确定应如何处理特定的点击。
在handleClick
函数的内部,我们使用switch
语句,该语句使用被单击的按钮的id
(即e.target.id
确定如何处理按钮的操作。 在下一节中,我们将看看switch语句在每种情况下会发生什么。
播放按钮
单击播放按钮后,我们需要更新应用程序的一些部分。 我们需要将播放图标切换为暂停图标。 如果currentTrackIndex
当前设置为0,我们还需要对其进行更新。为了更改应用程序的这两部分,我们将调用setState
,该函数可用于每个React组件。
setState
函数可用于所有React组件,这是我们更新音乐播放器状态的方式。 setState
函数中的第一个参数可以是对象或函数。 如果我们不依赖应用程序的当前状态来计算下一个状态,则使用对象作为第一个参数是一种很好的方法,它看起来像这样: this.setState({currentState:'newState'})
。 在我们的例子中,我们依靠应用程序的当前状态来确定应用程序的下一个状态,因此我们将要使用一个函数。 React文档指出了为什么这很重要:
React可以将多个
setState()
调用批处理到单个更新中以提高性能。 由于this.props
和this.state
可以异步更新,因此您不应依赖于它们的值来计算下一个状态。
随着React 16启用更多功能(包括异步渲染),这种区别将变得更加重要。
当单击播放按钮并调用setState
,我们传入一个函数,因为我们依赖于currentTrackIndex
状态变量的当前值。 传递给函数的第一个参数是应用程序的先前状态,第二个参数是当前props
。 在我们的例子中,我们只需要应用程序的上一个状态来确定下一个状态:
case "play":
this.setState((state, props) => {
let currentTrackIndex = state.currentTrackIndex;
if (currentTrackIndex === 0) {
currentTrackIndex = 1;
}
一旦我们设置currentTrackIndex
正确基础上的前值currentTrackIndex
,我们再回到我们想要更新的值的对象。 在单击播放按钮的情况下,我们将playing
布尔值更新为true
并设置currentTrackIndex
的值:
return {
playing: true,
currentTrackIndex: currentTrackIndex
};
传递给setState
函数的第二个参数是在setState
函数完成后调用的回调函数。 当单击播放按钮,并且我们的应用程序状态已更新时,我们要开始播放音乐。 我们传入this.playAudio
函数作为setState
函数的第二个参数。
},this.playAudio);
调用playAudio
按钮时,我们引用audio
标签,并通过Web Audio API调用我们可用的load
和play
功能。
playAudio(){
this.audioElement.load();
this.audioElement.play();
}
ref
DOM元素
为了引用实际的音频DOM元素来播放音频,我们需要使用可用于所有React组件的特殊属性ref
属性。 从React文档中:
在HTML元素上使用
ref
属性时,ref
回调将接收基础DOM元素作为其参数。
在我们的情况下,我们将ref
属性添加到audio
DOM元素,这使我们可以播放每个音轨的音频:
<audio ref={(audio)=>{this.audioElement = audio}} src={"/songs/"+this.state.currentTrackIndex+".mp3"}/>
暂停按钮
当单击暂停按钮时,我们调用this.setState
并将playing
布尔值设置为false
。
case "pause":
this.setState({ playing: false },this.pauseAudio);
break;
setState
函数调用的第二个参数是this.pauseAudio
函数,它引用audio
元素并调用pause()
函数。
pauseAudio(){
this.audioElement.pause();
}
<<(上一个)按钮
当单击<< <<图标时,上一个按钮的id
与switch语句的“ prev”大小写匹配,因此将执行与“ prev”大小写关联的代码。 在“上一个”情况下,我们使用类似于播放和暂停应用程序的函数再次调用this.setState()
。 这次,我们使用currentTrackIndex
的先前值减小该值,并返回一个对象以将currentTrackIndex
设置为新值。
case "prev":
this.setState((state, props) => {
let currentIndex = state.currentTrackIndex - 1;
if (currentIndex <= 0) {
return null;
} else {
return { playing:true,currentTrackIndex: currentIndex };
}
},this.playAudio);
从setState
返回null
React 16中的新变化之一是,当我们从setState
函数返回null
时,将不会重新渲染我们的应用程序。 我们的曲目列表有11个可用曲目。 如果用户继续单击<<按钮,则currentTrackIndex
会递减直到变为0。一旦变为0,我们就不再希望递减currentTrackIndex
并且不再需要重新渲染应用程序。 单击>>图标时,我们也将执行同样的操作。 如果currentTrackIndex
等于(或大于)列表(11)中的轨道数,则从setState
返回null
。
>>
(下一个)按钮
调用>>按钮时,我们具有与<<按钮类似的功能。 每次用户单击>> ,我们都会增加currentTrackIndex
并检查currentTrackIndex
是否不大于曲目列表的长度。 如果是,则在setState
函数调用中返回null
。
case "next":
this.setState((state, props) => {
let currentIndex = state.currentTrackIndex + 1;
if (currentIndex > data.tracks.length) {
return null;
} else {
return { playing:true,currentTrackIndex: currentIndex };
}
},this.playAudio);
break;
曲目清单
为了便于理解本文中的概念,我们已经在JSON文件中对曲目列表数据进行了硬编码。 我们从顶部的JSON文件导入数据,并在生命周期方法componentDidMount
设置TrackList
组件的状态。 TrackList
组件的状态包含一个变量,即tracks
变量。
Lifecyle方法componentDidMount
和componentDidUpdate
除了setState
函数之外,每个React组件还具有可用的生命周期方法。 我们的TrackList
组件使用其中两个, componentDidMount
和componentDidUpdate
。 componentDidMount
当阵营组件可在DOM被调用。 在这种情况下,我们想向组件中添加一些数据,因此在componentDidMount
调用setState
是合适的时间。
当我们的App
组件更新currentTrackIndex
,会触发TrackList
组件中的componentDidUpdate
方法,因为TrackList
组件正在获取新数据。 当TrackList
组件获取新数据时,我们要确保当前选定的轨道位于视口中,因此我们进行一些计算以确定当前选定的轨道在DOM中的位置,并使其出现在轨道列表容器的视图中。 。
componentDidUpdate() {
if (this.activeTrack) {
let topOfTrackList = this.trackList.scrollTop;
let bottomOfTrackList =
this.trackList.scrollTop + this.trackList.clientHeight;
let positionOfSelected = this.activeTrack.offsetTop;
if (
topOfTrackList > positionOfSelected ||
bottomOfTrackList < positionOfSelected
) {
this.trackList.scrollTop = positionOfSelected;
}
}
}
显示曲目列表
我们使用JavaScript map
函数遍历轨道数组,并为数组中的每个元素调用一个函数。 我们调用的函数是renderListItem
,它包含一些逻辑来确定currentTrackIndex
是否是我们正在渲染的数组中的当前元素。 如果是的话,我们需要确保li
上的className
值包括selected
字符串。 这样可以确保所选曲目的样式与列表的其余部分相比将有所不同。
renderListItem(track, i) {
let trackClass = this.props.currentTrackIndex === track.id
? "selected"
: "";
return (
<li
key={track.id}
className={trackClass}
ref={cur => {
if (this.props.currentTrackIndex === track.id) {
this.activeTrack = cur;
}
}}
onClick={()=>{this.props.selectTrackNumber(track.id)}}
>
<div className="number">{track.id}</div>
<div className="title">{track.title}</div>
<div className="duration">{track.duration}</div>
</li>
);
}
li
元素还包含其他一些重要属性:
key
:每当我们有一个列表时,我们都需要包含此属性,以便列表能够正确呈现。 有关在React中将键与列表一起使用的更多信息, 请参阅React文档中的本文 。className
:如果li
是当前选定的音轨,请确保li
附加了selected
类别。ref
:我们使用ref
属性来计算轨道列表容器的正确位置。 如果当前轨迹不可见,我们将计算当前轨迹的位置并使它可见。 我们需要访问实际的DOM元素才能正确进行此计算。onClick
:当用户选择特定曲目时,我们调用此函数,该函数调用this.props.selectTrackNumber
。 该函数从父App
组件传递到TrackList
组件,就像Controls
组件的单击处理程序一样。 调用此函数时,我们的应用程序状态将更新,同时currentTrackIndex
设置为用户选择的曲目号。
selectTrackNumber(trackId){
this.setState({currentTrackIndex:trackId,playing:true},this.playAudio);
}
试试看!
查看Codepen示例。 专辑封面来自一个叫做Glass Animals的乐队的专辑。 由于我们无法合法地播放“玻璃动物”原声带,因此,我们选择了一些免版税的音乐来代替它播放,因此我们可以充分发挥音乐播放器的作用。
见笔阵营DailyUI - 009 -音乐播放器由杰克·奥利弗( @jackoliver )上CodePen 。
此帖子是Fullstack React的React Daily UI帖子系列的一部分,该系列是Jack Oliver , Sophia Shoemaker和Fullstack React团队的其他成员共同努力的结果。
是否想深入研究React基础知识? 查看Fullstack React:ReactJS和Friends完整指南以了解更多信息。