Previously, we discussed React's presentation components with few examples backing it up. This is the last section of the "Build a Music Player with React & Electron" series and we will discuss container components while completing our Scotch Player Electron app.
之前 ,我们讨论了React的演示组件,并提供了一些支持它的示例。 这是“使用React&Electron构建音乐播放器”系列的最后一部分,我们将在完成Scotch Player Electron应用程序的同时讨论容器组件。
Go ahead and look back at the previous two articles if you need a refresher:
如果需要复习,请继续阅读前两篇文章:
- Part I: Setup & Basic Concepts (this article) 第一部分:设置和基本概念 (本文)
- Part 2: Making the UI 第2部分:制作UI
重新访问我们的应用目录结构 ( Revisit Our app Directory Structure )
Just as we did in the previous section, let us have another look at the directory structure:
就像我们在上一节中所做的一样,让我们再来看一下目录结构:
| ---app #All React projects goes here
| ----components # Presentation Component Directory
| ------details.component.js # Completed
| ------footer.component.js # Completed
| ------player.component.js # Completed
| ------progress.component.js # Completed
| ------search.component.js # Completed
| ----containers # Container Component Directory
| ------app.container.js # WHERE WE ARE
| ----app.js # SOME CHANGES WILL GO HERE
The comments will give you an idea of where we are headed and what we have achieved (if you stumbled on this article first). Our focus is to flesh out the app/containers/app.container.js
and make some changes to app/app.js
. Let's begin!
这些评论将使您大致了解我们的发展方向和取得的成就(如果您首先偶然发现本文)。 我们的重点是app/containers/app.container.js
并对app/app.js
进行一些更改。 让我们开始!
我们的容器组件 ( Our Container Component )
Just as a recap, this is the type of component that handles the dirty jobs which we abstracted from presentation component. These jobs include:
就像回顾一样,这是处理从表示组件中提取的脏工作的组件类型。 这些工作包括:
- state mutation 状态突变
- event handling 事件处理
- composing different presentation components together 组成不同的演示组件
For easy understanding, we will talk about each UI component as we import them one after the other into the container component. We also define the states as we meet them in the short journey. Below is a skeleton of app.container.js
:
为了便于理解,我们将讨论每个UI组件,因为它们一个接一个地将其导入到容器组件中。 我们还会在短期旅程中与州会面时定义州。 以下是app.container.js
的框架:
// React library
import React from 'react' ;
// Axios of Ajax
import Axios from 'axios' ;
// AppContainer class
class AppContainer extends React . Component {
// AppContainer constructor
constructor ( props ) {
super ( props ) ;
}
// componentDidMount lifecycle method. Called once a component is loaded
componentDidMount ( ) {
this . randomTrack ( ) ;
}
// Render method
render ( ) {
return (
< div className = "scotch_music" >
< / div >
) ;
}
}
// Export AppContainer Component
export default AppContainer
Life cycle methods For every given time in a component's life cycle, a given method is provided to handle certain tasks. Starting from before the component even loads to when it is completely loaded and some others. One life cycle method is
componentDidMount()
and is called after it is loaded生命周期方法对于组件生命周期中的每个给定时间,都会提供给定方法来处理某些任务。 从组件即使负载时它完全加载之前启动其他一些人 。 一种生命周期方法是
componentDidMount()
,它在加载后被调用
When the component is fully loaded we want to pick a track from Soundcloud to play. componentDidMoint
is already calling a member method which returns a random track using Ajax
. We can make Ajax
request with Axios:
组件完全加载后,我们想从Soundcloud中选择要播放的曲目。 componentDidMoint
已经在调用成员方法,该方法使用Ajax
返回随机轨道。 我们可以使用Axios发出Ajax
请求:
randomTrack ( ) {
let _this = this ;
//Request for a playlist via Soundcloud using a client id
Axios . get ( `https://api.soundcloud.com/playlists/209262931?client_id= ${ this . client_id } ` )
. then ( function ( response ) {
// Store the length of the tracks
const trackLength = response . data . tracks . length ;
// Pick a random number
const randomNumber = Math . floor ( ( Math . random ( ) * trackLength ) + 1 ) ;
// Set the track state with a random track from the playlist
_this . setState ( { track : response . data . tracks [ randomNumber ] } ) ;
} )
. catch ( function ( err ) {
// If something goes wrong, let us know
console . log ( err ) ;
} ) ;
}
What have we done? Request for a particular playlist on Soundcloud, use a random number to pick a track from the returned array, set the track to the track
state. The track
state initial state needs to be defined:
我们做了什么? 在Soundcloud上请求特定的播放列表,使用随机数从返回的数组中选择曲目,并将曲目设置为track
状态。 track
状态的初始状态需要定义:
// ...
constructor ( props ) {
super ( props ) ;
// Client ID
this . client_id = 'YOUR_CLIENT_ID' ;
// Initial State
this . state = {
// What ever is returned, we just need these 3 values
track : { stream_url : '' , title : '' , artwork_url : '' }
}
}
//...
React声音 ( React Sound )
React Sound is a component that wraps around Sound Manager 2 and expose common sound APIs like
React Sound是环绕Sound Manager 2并公开常见声音API的组件,例如
- play 玩
- pause 暂停
- stop 停
- etc 等等
We already installed it using package.json
, so let's start with the import:
我们已经使用package.json
安装了它,所以让我们从导入开始:
// React library
import React from 'react' ;
// Axios of Ajax
import Axios from 'axios' ;
// Sound component
import Sound from 'react-sound' ;
// ...
... then include the component in render's return like so:
...然后将组件包含在render的返回中,如下所示:
//...
render ( ) {
return (
< div className = "scotch_music" >
< Sound
url = { this . prepareUrl ( this . state . track . stream_url ) }
playStatus = { this . state . playStatus }
onPlaying = { this . handleSongPlaying . bind ( this ) }
playFromPosition = { this . state . playFromPosition }
onFinishedPlaying = { this . handleSongFinished . bind ( this ) } / >
< / div >
) ;
}
//...
From just adding that, you have a top level overview of the properties that we need to supply Sound
. We already started by supplying undefined values and callbacks. Let us take them one after the other:
通过添加这些内容,您将获得我们需要提供Sound
的属性的顶级概述。 我们已经开始提供未定义的值和回调。 让我们一个接一个地将它们带走:
url
: This is the stream URL of the sound. We are using Soundcloud stream URLs in this case which requires a client id else you will get a 401
. We will use a utility function (prepareUrl
) to attach the client id to the URL:
url
:这是声音的流URL。 在这种情况下,我们使用的是Soundcloud流URL,它需要一个客户端ID,否则您将获得401
。 我们将使用实用程序功能( prepareUrl
)将客户端ID附加到URL:
//...
// A method in the AppContainer class
prepareUrl ( url ) {
// Attach client id to stream url
return ` ${ url } ?client_id= ${ this . client_id } `
}
//...
... then define the client id in the class constructor:
...然后在类构造函数中定义客户端ID:
// ...
constructor ( props ) {
super ( props ) ;
this . client_id = 'YOUR_CLIENT_ID' ;
}
// ...
The URL is also gotten from a track
state which we already defined.
该URL也来自我们已经定义的track
状态。
playStatus
has the value this.state.playStatus
therefore will have an initial state too:
playStatus
的值为this.state.playStatus
因此也将具有初始状态:
//...
constructor ( props ) {
this . state = {
// What ever is returned, we just need these 3 values
track : { stream_url : '' , title : '' , artwork_url : '' } ,
playStatus : Sound . status . STOPPED ,
}
}
//...
React Sound has 3 play statuses:
React Sound具有3种播放状态:
Sound.status.STOPPED
Sound.status.STOPPED
Sound.status.PAUSED
Sound.status.PAUSED
Sound.status.PLAYING
Sound.status.PLAYING
We want the sound player to stay stopped even when the URL is available so the user can manually play it. That is why the initial state is Sound.status.STOPPED
. Keep in mind that this is the state that we change when play or pause is clicked.
我们希望声音播放器即使在URL可用时也保持停止状态,以便用户可以手动播放它。 这就是为什么初始状态为Sound.status.STOPPED
。 请记住,这是单击播放或暂停时我们更改的状态。
onPlaying
: This one does not require a value but a method to be called while the track is playing. This method is handy when we need to update elapsed time of the sound. The value is this.handleSongPlaying.bind(this)
which is a AppContainer
member method:
onPlaying
:此值不需要值,但是在播放轨道时要调用的方法。 当我们需要更新声音的经过时间时,此方法很方便。 值是this.handleSongPlaying.bind(this)
,它是AppContainer
成员方法:
handleSongPlaying ( audio ) {
this . setState ( { elapsed : this . formatMilliseconds ( audio . position ) ,
total : this . formatMilliseconds ( audio . duration ) ,
position : audio . position / audio . duration } )
}
It receives the current status of the audio instance which gives us access to the audio properties like position
and duration
它接收音频实例的当前状态,这使我们可以访问音频属性,例如position
和duration
We are also updating 3 more states here which needs to be set in the initial state:
我们还将在此处更新3个需要设置为初始状态的状态:
// ...
constructor ( props ) {
this . state = {
// What ever is returned, we just need these 3 values
track : { stream_url : '' , title : '' , artwork_url : '' } ,
playStatus : Sound . status . STOPPED ,
elapsed : '00:00' ,
total : '00:00' ,
position : 0
}
}
// ...
elapsed
state is set with the current position of the audio. 音频的当前位置设定elapsed
状态。total
state is set with the estimated total play time of the audio. 利用估计的音频总播放时间设置total
状态。position
state is used for the progress bar (yet to be seen)position
状态用于进度条(尚未看到)
These 3 states are handy for the Progress
component which we will use later.
这三个状态对于“ Progress
组件很方便,我们将在以后使用。
We are using another utility method formatMilliseconds
to format time:
我们正在使用另一种实用程序方法formatMilliseconds
来格式化时间:
formatMilliseconds ( milliseconds ) {
// Format hours
var hours = Math . floor ( milliseconds / 3600000 ) ;
milliseconds = milliseconds % 3600000 ;
// Format minutes
var minutes = Math . floor ( milliseconds / 60000 ) ;
milliseconds = milliseconds % 60000 ;
// Format seconds
var seconds = Math . floor ( milliseconds / 1000 ) ;
milliseconds = Math . floor ( milliseconds % 1000 ) ;
// Return as string
return ( minutes < 10 ? '0' : '' ) + minutes + ':' +
( seconds < 10 ? '0' : '' ) + seconds ;
}
playFromPosition
: We would always have a reason to update the play position. Reasons like rewind (backward) or fast-forward. Takes another state which will be updated when the rewind or forwad buttons are clicked. Let us include in our initial state definition:
playFromPosition
:我们总是有理由更新播放位置。 倒带(快退)或快进等原因。 当单击快退或前进按钮时,将采用另一种状态,该状态将被更新。 让我们在初始状态定义中添加:
//...
constructor ( props ) {
this . state = {
// What ever is returned, we just need these 3 values
track : { stream_url : '' , title : '' , artwork_url : '' } ,
playStatus : Sound . status . STOPPED ,
elapsed : '00:00' ,
total : '00:00' ,
position : 0 ,
playFromPosition : 0
}
}
//...
We just initialize it with the value of 0.
我们只是将其初始化为0。
onFinishedPlaying
: This receives a callback also which is called after a particular track is done playing. We passed in handleSongFinished
, let's define that:
onFinishedPlaying
:这也接收一个回调,在完成特定轨道的播放后也会调用该回调。 我们传入handleSongFinished
,让我们定义一下:
handleSongFinished ( ) {
// Call random Track
this . randomTrack ( ) ;
}
Quite straight-forward! Call the randomTrack()
member method after a track is done playing. This means another track from the playlist will begin to play.
很简单! 播放randomTrack()
曲目后,调用randomTrack()
成员方法。 这意味着播放列表中的另一首曲目将开始播放。
搜索音乐 ( Searching for Music )
This component wraps our auto-complete functionality. Let us start with importing it:
该组件包装了我们的自动完成功能。 让我们从导入它开始:
// ...
import Search from '../components/search.component' ;
// ...
... then include the component in render's return like so:
...然后将组件包含在render的返回中,如下所示:
//...
render ( ) {
return (
< div className = "scotch_music" >
< Search
autoCompleteValue = { this . state . autoCompleteValue }
tracks = { this . state . tracks }
handleSelect = { this . handleSelect . bind ( this ) }
handleChange = { this . handleChange . bind ( this ) } / >
{ /*...*/ }
< / div >
) ;
}
//...
Recall that our UI component uses props to communicate with the container component. Both values and callbacks are passed in as props:
回想一下,我们的UI组件使用道具与容器组件进行通信。 值和回调都作为props传递:
autoCompleteValue
: Simply the value of the auto-complete input/search box. It receives a state and the state needs to be defined:
autoCompleteValue
:简单是自动完成输入/搜索框的值。 它接收一个状态,并且该状态需要定义:
constructor ( props ) {
//...
this . state = {
// ...other states
autoCompleteValue : ''
} ;
}
tracks
: The auto-complete items shown in the drop-down has to be a list of tracks we is an array of Soundcloud tracks. It's initial state is an empty array:
tracks
:下拉列表中显示的自动完成项必须是曲目列表,我们是Soundcloud曲目的数组。 它的初始状态是一个空数组:
constructor ( props ) {
//...
this . state = {
// ...other states
autoCompleteValue : '' ,
tracks : [ ]
} ;
}
handleSelect
: Receives a callback which is called when an item on the auto-complete's drop-down is clicked. The value is a member method handleSelect
:
handleSelect
:接收一个回调,该回调在单击自动完成下拉菜单中的项目时被调用。 该值是成员方法handleSelect
:
handleSelect ( value , item ) {
this . setState ( { autoCompleteValue : value , track : item } ) ;
}
We are updating the state of two values, autoCompleteValue
which will just set the input box with what we are entering and track which will change the current song that is playing to what we selected.
我们正在更新两个值的状态: autoCompleteValue
,它将仅将输入内容设置为输入框,而track则会将正在播放的当前歌曲更改为我们选择的内容。
handleChange
: Also receives a callback which is called when the value of the auto-complete's input box changes. The value is a member method handleChange
:
handleChange
:还接收一个回调,当自动完成输入框的值更改时,将调用该回调。 该值是成员方法handleChange
:
handleChange ( event , value ) {
// Update input box
this . setState ( { autoCompleteValue : event . target . value } ) ;
let _this = this ;
//Search for song with entered value
Axios . get ( `https://api.soundcloud.com/tracks?client_id= ${ this . client_id } &q= ${ value } ` )
. then ( function ( response ) {
// Update track state
_this . setState ( { tracks : response . data } ) ;
} )
. catch ( function ( err ) {
console . log ( err ) ;
} ) ;
}
First we update the value in the input box and then use Axios to search for songs on Soundcloud based on what is entered in the input box.
首先,我们更新输入框中的值,然后使用Axios根据在输入框中输入的内容在Soundcloud上搜索歌曲。
音乐详情 ( Music Details )
This component as we already saw, expects just the track title which it will display with h3
:
正如我们已经看到的,这个组件只期望将在h3
显示的曲目标题:
//... other imports
//Details components
import Details from '../components/details.component' ;
class AppContainer extends React . Component {
constructor ( props ) {
super ( props ) ;
this . state = {
track : { stream_url : '' , title : '' , artwork_url : '' } ,
//... other states
} ;
}
render ( ) {
return (
< div className = "scotch_music" >
//... other components
< Details
title = { this . state . track . title } / >
//...other components
< / div >
) ;
}
}
export default AppContainer
The title property is supplied with the title of the playing track.
title属性随播放曲目的标题一起提供。
音乐播放器控件 ( Music Player Controls )
The player component is filled with series of callbacks because it is made up of just control buttons:
播放器组件由一系列回调组成,因为它仅由控制按钮组成:
//... other imports
//Details components
import Player from '../components/player.component' ;
class AppContainer extends React . Component {
//... other members
render ( ) {
return (
< div className = "scotch_music" >
//... other components
< Player
togglePlay = { this . togglePlay . bind ( this ) }
stop = { this . stop . bind ( this ) }
playStatus = { this . state . playStatus }
forward = { this . forward . bind ( this ) }
backward = { this . backward . bind ( this ) }
random = { this . randomTrack . bind ( this ) } / >
//...other components
< / div >
) ;
}
}
export default AppContainer
togglepplay
: Receives a member method as callback, togglePlay
:
togglepplay
:接收一个成员方法作为回调, togglePlay
:
togglePlay ( ) {
// Check current playing state
if ( this . state . playStatus === Sound . status . PLAYING ) {
// Pause if playing
this . setState ( { playStatus : Sound . status . PAUSED } )
} else {
// Resume if paused
this . setState ( { playStatus : Sound . status . PLAYING } )
}
}
Simply runs a check on the current play status, if it is playing, the sound will be paused when the button is clicked and vice versa.
只需检查当前的播放状态,如果正在播放,单击该按钮时声音将暂停,反之亦然。
stop
: Just as the name goes, halts a current sound and resets it's position to 0 when played again. Receives a callback, stop
:
stop
:顾名思义,再次播放时,暂停当前声音并将其位置重置为0。 收到回调, stop
:
stop ( ) {
// Stop sound
this . setState ( { playStatus : Sound . status . STOPPED } ) ;
}
We are just updating the playStatus
state.
我们只是在更新playStatus
状态。
forward
: You can also refer to it as fast-forward
We want to push the song +10 sec when it is clicked. Receives forward
method as callback:
forward
:您也可以将其称为fast-forward
我们希望在单击歌曲时将其推+10秒。 接收forward
方法作为回调:
forward ( ) {
this . setState ( { playFromPosition : this . state . playFromPosition += 1000 * 10 } ) ;
}
backward
: Just like forward but -10sec:
backward
:就像向前,但-10秒:
backward ( ) {
this . setState ( { playFromPosition : this . state . playFromPosition -= 1000 * 10 } ) ;
}
random
: We also want our app to have a cool feature of manually picking a random track from the playlist we are using. To do so we just call the randomTrack
method when the button is clicked. The method is available above as we have used it before in componentDidMount
.
random
:我们还希望我们的应用具有一个很酷的功能,可以从正在使用的播放列表中手动选择随机曲目。 为此,我们只需在单击按钮时调用randomTrack
方法。 该方法在上面可用,因为我们之前在componentDidMount
使用过它。
歌曲进度条 ( Song Progress Bar )
The progress component's properties only receive states. States which previously, we have seen how and why they exist. Just as a recap, they include: elapsed
, total
and position
:
进度组件的属性仅接收状态。 在以前的国家中,我们已经看到了它们的存在方式和原因。 作为回顾,它们包括: elapsed
, total
和position
:
< Progress
elapsed = { this . state . elapsed }
total = { this . state . total }
position = { this . state . position } / >
页脚 ( Footer )
This guy does not even deserve a subtitle because there is nothing to discuss about it. It is just a static component with no state or behavior:
这个家伙甚至都不配字幕,因为没有什么可以讨论的。 它只是一个静态组件,没有状态或行为:
< Footer / >
背景图片艺术品技巧 ( Background Image Artwork Trick )
From the peek of how our app looks which was show in the first section, the background of the app changes with each track's artwork. Colors in such images are unpredictable and might conflict with the components' colors. The trick is to use background overlay:
从第一部分显示的应用外观来看,应用的背景随每首曲目的艺术品而变化。 此类图像中的颜色是无法预测的,并且可能与组件的颜色冲突。 诀窍是使用背景叠加 :
// app.component.js
// ...other members
render ( ) {
const scotchStyle = {
width : '500px' ,
height : '500px' ,
backgroundImage : `linear-gradient(
rgba(0, 0, 0, 0.7),
rgba(0, 0, 0, 0.7)
), url( ${ this . xlArtwork ( this . state . track . artwork_url ) } )`
}
return (
< div className = "scotch_music" style = { scotchStyle } >
{ /*other components*/ }
//...
Soundcloud returns a small (though Soundcloud calls it large) artwork URL so we needed a util method to replace it with a large one (Soundcloud calls it t500
):
Soundcloud返回一个很小的(尽管Soundcloud称它为大)图稿URL,因此我们需要一种util方法将其替换为一个较大的图稿URL(Soundcloud称它为t500
):
xlArtwork ( url ) {
return url . replace ( /large/ , 't500x500' ) ;
}
Long story short, this is how our app.component.js
looks:
长话短说,这就是我们的app.component.js
外观:
//React library
import React from 'react' ;
//Sound component
import Sound from 'react-sound' ;
//Axios for Ajax
import Axios from 'axios' ;
//Custom components
import Details from '../components/details.component' ;
import Player from '../components/player.component' ;
import Progress from '../components/progress.component' ;
import Search from '../components/search.component' ;
import Footer from '../components/footer.component' ;
class AppContainer extends React . Component {
constructor ( props ) {
super ( props ) ;
this . client_id = '2f98992c40b8edf17423d93bda2e04ab' ;
this . state = {
track : { stream_url : '' , title : '' , artwork_url : '' } ,
tracks : [ ] ,
playStatus : Sound . status . STOPPED ,
elapsed : '00:00' ,
total : '00:00' ,
position : 0 ,
playFromPosition : 0 ,
autoCompleteValue : ''
} ;
}
componentDidMount ( ) {
this . randomTrack ( ) ;
}
prepareUrl ( url ) {
//Attach client id to stream url
return ` ${ url } ?client_id= ${ this . client_id } `
}
xlArtwork ( url ) {
return url . replace ( /large/ , 't500x500' ) ;
}
togglePlay ( ) {
// Check current playing state
if ( this . state . playStatus === Sound . status . PLAYING ) {
// Pause if playing
this . setState ( { playStatus : Sound . status . PAUSED } )
} else {
// Resume if paused
this . setState ( { playStatus : Sound . status . PLAYING } )
}
}
stop ( ) {
// Stop sound
this . setState ( { playStatus : Sound . status . STOPPED } ) ;
}
forward ( ) {
this . setState ( { playFromPosition : this . state . playFromPosition += 1000 * 10 } ) ;
}
backward ( ) {
this . setState ( { playFromPosition : this . state . playFromPosition -= 1000 * 10 } ) ;
}
handleSelect ( value , item ) {
this . setState ( { autoCompleteValue : value , track : item } ) ;
}
handleChange ( event , value ) {
// Update input box
this . setState ( { autoCompleteValue : event . target . value } ) ;
let _this = this ;
//Search for song with entered value
Axios . get ( `https://api.soundcloud.com/tracks?client_id= ${ this . client_id } &q= ${ value } ` )
. then ( function ( response ) {
// Update track state
_this . setState ( { tracks : response . data } ) ;
} )
. catch ( function ( err ) {
console . log ( err ) ;
} ) ;
}
formatMilliseconds ( milliseconds ) {
var hours = Math . floor ( milliseconds / 3600000 ) ;
milliseconds = milliseconds % 3600000 ;
var minutes = Math . floor ( milliseconds / 60000 ) ;
milliseconds = milliseconds % 60000 ;
var seconds = Math . floor ( milliseconds / 1000 ) ;
milliseconds = Math . floor ( milliseconds % 1000 ) ;
return ( minutes < 10 ? '0' : '' ) + minutes + ':' +
( seconds < 10 ? '0' : '' ) + seconds ;
}
handleSongPlaying ( audio ) {
this . setState ( { elapsed : this . formatMilliseconds ( audio . position ) ,
total : this . formatMilliseconds ( audio . duration ) ,
position : audio . position / audio . duration } )
}
handleSongFinished ( ) {
this . randomTrack ( ) ;
}
randomTrack ( ) {
let _this = this ;
//Request for a playlist via Soundcloud using a client id
Axios . get ( `https://api.soundcloud.com/playlists/209262931?client_id= ${ this . client_id } ` )
. then ( function ( response ) {
// Store the length of the tracks
const trackLength = response . data . tracks . length ;
// Pick a random number
const randomNumber = Math . floor ( ( Math . random ( ) * trackLength ) + 1 ) ;
//Set the track state with a random track from the playlist
_this . setState ( { track : response . data . tracks [ randomNumber ] } ) ;
} )
. catch ( function ( err ) {
//If something goes wrong, let us know
console . log ( err ) ;
} ) ;
}
render ( ) {
const scotchStyle = {
width : '500px' ,
height : '500px' ,
backgroundImage : `linear-gradient(
rgba(0, 0, 0, 0.7),
rgba(0, 0, 0, 0.7)
), url( ${ this . xlArtwork ( this . state . track . artwork_url ) } )`
}
return (
< div className = "scotch_music" style = { scotchStyle } >
< Search
clientId = { this . state . client_id }
autoCompleteValue = { this . state . autoCompleteValue }
tracks = { this . state . tracks }
handleSelect = { this . handleSelect . bind ( this ) }
handleChange = { this . handleChange . bind ( this ) } / >
< Details
title = { this . state . track . title } / >
< Sound
url = { this . prepareUrl ( this . state . track . stream_url ) }
playStatus = { this . state . playStatus }
onPlaying = { this . handleSongPlaying . bind ( this ) }
playFromPosition = { this . state . playFromPosition }
onFinishedPlaying = { this . handleSongFinished . bind ( this ) } / >
< Player
togglePlay = { this . togglePlay . bind ( this ) }
stop = { this . stop . bind ( this ) }
playStatus = { this . state . playStatus }
forward = { this . forward . bind ( this ) }
backward = { this . backward . bind ( this ) }
random = { this . randomTrack . bind ( this ) } / >
< Progress
elapsed = { this . state . elapsed }
total = { this . state . total }
position = { this . state . position } / >
< Footer / >
< / div >
) ;
}
}
export default AppContainer
Note that we export AppContainer
so it can be imported in app.js
请注意,我们导出了AppContainer
因此可以将其导入到app.js
重构app.js ( Refactoring app.js )
In the previous article, our app.js
's App component is the guy importing and using our UI components. Now we have shifted that responsibility to AppContainer
, therefore, our app.js
will end up looking like this:
在上一篇文章中,我们的app.js
的App组件是导入和使用我们的UI组件的家伙。 现在我们已将责任转移到AppContainer
,因此,我们的app.js
最终将如下所示:
//React libraries
import React from 'react' ;
import ReactDOM from 'react-dom' ;
//Import Container component
import AppContainer from './containers/app.container'
class App extends React . Component {
render ( ) {
return (
< AppContainer / >
) ;
}
}
// Render to index.html
ReactDOM . render (
< App / > ,
document . getElementById ( 'content' )
) ;
The Codepen demo is not for code perusing as I just dumped the bundle.js into it. Rather, it is a good play guide of what we built
Codepen演示不适用于代码阅读,因为我只是将bundle.js转储到了其中。 而是,它是我们制作内容的不错的游戏指南
结论 ( Conclusion )
We had a bit of a lot of things: Electron, React, Babel, Browserify, Soundcloud, Axios, etc. Just one matters a lot on the long run and that is React. This is the end of Journey for this series and hopefully you understand what React is, why it exists, its fundamentals, and its use cases. There are a lot to still learn about React (React Router, Redux, etc) which Scotch will be bringing to your table in no distant time.
我们有很多东西:Electron,React,Babel,Browserify,Soundcloud,Axios等。从长远来看,只有一件事情很重要,那就是React。 这是本系列旅程的结尾,希望您理解React是什么,为什么存在,它的基本原理和用例。 关于Scotch将在不远的时间内带到您的表的React(React Router,Redux等)还有很多东西要学习。
翻译自: https://scotch.io/tutorials/build-a-music-player-with-react-electron-iii-bringing-it-all-together