使用React&Electron III打造音乐播放器:将一切融合在一起

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:

如果需要复习,请继续阅读前两篇文章:

重新访问我们的应用目录结构 ( 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

它接收音频实例的当前状态,这使我们可以访问音频属性,例如positionduration

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:

进度组件的属性仅接收状态。 在以前的国家中,我们已经看到了它们的存在方式和原因。 作为回顾,它们包括: elapsedtotalposition

< Progress
          elapsed = { this . state . elapsed }
          total = { this . state . total }
          position = { this . state . position } / >

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' )
) ;

React和Electron音乐播放器

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值