2017-05-20 9 views
0

私は今2-3週間反応を学習してきました。私はJavaScriptで数年の経験があります。React Routerを使用しているときに、ネストされたコンポーネントにメソッドと状態を渡すにはどうすればよいですか?

私は、アプリケーションのアーキテクチャがReactコンポーネントを使ってどのように動作するかに慣れるために、Reactでmp3プレーヤーを作っています。 私はいくつかの単純なコンポーネントでmp3を動作させることに成功しました。

私はアプリケーションを拡張することに決めました。私はいくつかのWebページを開発したいと思います。 私はReact Routerをどのように使用して異なる "ページ"にナビゲートするかを学び始めました。 しかし、ルーティング時に、親コンポーネントから子コンポーネントに状態とメソッドを渡すときに問題が発生しました。

アプリケーションコンポーネントには、Header、AudioPlayer、およびControlsコンポーネントが含まれています。これらのコンポーネントはアプリケーションのすべてのページの一部になるためです。 (注:コントロールコンポーネントは、「戻る」、「進む」、「再生」および「一時停止」ボタンとタイマーバーで構成され、現在のオーディオの進行状況を表示します)

ルートのパスが「曲」の場合、 「サウンド」コンポーネントを表示します。 Soundsコンポーネントは、アプリケーションコンポーネントで定義されたselectSoundメソッドとcurrentSoundIndex状態を取る必要があります。 これを達成する方法はありますか?

私はまた、リアクションのベストプラクティスに従っていきたいと思っています。このアプリケーションを構成するためのより良い方法があれば、知りたいと思います。 (例:Reduxを使用すべきですか?) 助けてくれてありがとう。

var{Router, 
    Route, 
    IndexRoute, 
    IndexLink, 
    hashHistory, 
    Link } = ReactRouter; 


var soundsData = []; 
var allSounds = [{"title" : "Egyptian Beat", "artist" : "Sarah Monks", "length": 16, "mp3" : "sounds/0010_beat_egyptian.mp3"}, 
     {"title" : "Euphoric Beat", "artist" : "Sarah Monks", "length": 31, "mp3" : "sounds/0011_beat_euphoric.mp3"}, 
     {"title" : "Latin Beat", "artist" : "Sarah Monks", "length": 59, "mp3" : "sounds/0014_beat_latin.mp3"}, 
     {"title" : "Pop Beat", "artist" : "Sarah Monks", "length": 24, "mp3" : "sounds/0015_beat_pop.mp3"}, 
     {"title" : "Falling Cute", "artist" : "Sarah Monks", "length": 3, "mp3" : "sounds/0027_falling_cute.mp3"}, 
     {"title" : "Feather", "artist" : "Sarah Monks", "length": 6, "mp3" : "sounds/0028_feather.mp3"}, 
     {"title" : "Lose Cute", "artist" : "Sarah Monks", "length": 3, "mp3" : "sounds/0036_lose_cute.mp3"}, 
     {"title" : "Pium", "artist" : "Sarah Monks", "length": 3, "mp3" : "sounds/0039_pium.mp3"}]; 

var favorites = []; 

soundsData[0] = allSounds; 
soundsData[1] = favorites; 

console.log(soundsData[0][0].title); 

var Header = function(props) { 
    //this is a stateless component and is a child component of the application component. 
    //it consists of the header navbar of the application 
    return (<header> 
     <ul className="header-nav" > 
      <li className="header-menu-item"> 
       <IndexLink to="/" className="header-menu-link" activeClassName="active">Home</IndexLink> 

      </li> 
      <li className="header-menu-item"> 
       <Link to="/songs" className="header-menu-link" activeClassName="active">Songs</Link> 

      </li> 
       <li className="header-menu-item navicon" onClick={props.toggleSidePanel} > 
       <span className="header-menu-link"> 
        <i className="fa fa-navicon"></i> 
       </span> 
      </li>     
     </ul> 
     </header> 
    ); 
} 

var Application = React.createClass({ 
    //This class is the main component of the application. 
    //it takes in the array of soundsData as a property. 
    getInitialState: function() {  
     return { 
      sidePanelIsOpen: false, 
      currentSoundIndex: 0, 
      isPlaying: false, 
      playerDuration: 0, 
      currentTime: "0:00", 
      currentWidthOfTimerBar: 0, 
      backButtonIsDisabled: false, 
      forwardButtonIsDisabled: false, 
      playButtonIsDisabled: false 
     } 
    }, 

    toggleSidePanel: function(){ 
     var sidePanelIsOpen = this.state.sidePanelIsOpen; 
     this.setState({sidePanelIsOpen: !sidePanelIsOpen}); 
    }, 
    componentDidMount: function() { 
      this.player = document.getElementById('audio_player'); 
     }, 
    loadPlayer: function(){ 
     this.player.load(); 
    }, 
    playSound: function(){ 
     clearInterval(this.currentWidthInterval); 
     this.setState({isPlaying: true}); 
     this.player.play(); 

     var sounds = this.props.route.sounds[0]; 

     var currentIndex = this.state.currentSoundIndex; 
     var duration = sounds[currentIndex].length; //this.player.duration; 
     //calculate what the width of the timer bar will be per second. 
     //98% is the total with of the timer bar 
     //we will change the width of the timer bar with CSS while the sound is playing. see the TimerBar component. 
     var widthPerSecond = 98/duration; 
     //need to store "this" into a variable as it will otherwise be out of scope in the setInterval method 
     var self = this; 

     this.currentWidthInterval = setInterval(function(){self.updateTimer(widthPerSecond); console.log('self ' + self.state.currentWidthOfTimerBar); console.log('self load time ' + self.player.currentTime); console.log("duration " + duration);}, 100); 
    }, 
    pauseSound: function(){ 
     this.setState({isPlaying: false}); 
     this.player.pause(); 
     clearInterval(this.currentWidthInterval); 
    }, 
    stopPlayer: function() { 
     this.player.pause(); 
     this.player.currentTime = 0; 
     this.setState({currentWidthOfTimerBar: 0}); 
     this.setState({currentTime: secondsToMins(this.player.currentTime)}); 
     clearInterval(this.currentWidthInterval); 

    }, 
    playPauseSound: function(){ 
     //this function is called when the play/pause toggle button is pressed. 
     if(this.state.isPlaying){ 
      //if the player is in a state of "isPlaying" then we call the pauseSound() method 
      this.pauseSound(); 
     }else{ 
      //if the player is currently paused (ie the state of "isPlaying" is false) then we call the playSound() method 
      this.playSound(); 
     } 
    }, 
    updateTimer: function (widthPerSecond){ 
     //Whenever the playSound() method is called, this method will run every 100 milliseconds. 
     //it will update the timer bar so we can see the progress on the current sound. 
     //it will also check to see if the current sound has reached the end of the duration so we can navigate to the next one. 

     //get the current time of the current sound that is playing 
     var currentTime = this.player.currentTime; 

     //calculate the current width of the timer bar so that we can update the CSS width. 
     var currentWidthOfTimerBar = currentTime*widthPerSecond; 

     //console.log('this.player.duration ' + this.player.duration); 

     this.setState({currentWidthOfTimerBar: currentWidthOfTimerBar}); 
     this.setState({currentTime: secondsToMins(currentTime)}); 

     //method cut short here for stackoverflow question 
    }, 
    selectSound: function(i){ 
     //if user selects a sound then we should firstly stop the player. 
     this.stopPlayer(); 
     //set the currentSoundIndex to be the index of the selected list item 
     this.setState({currentSoundIndex: i},() => { 
      //we need to load the player as a new src has been inserted. 
      this.loadPlayer(); 
      if(this.state.isPlaying){ 
       //if the player is in a state of playing come here 
       this.playSound(); 
      }  
     }); 
    }, 
    goToPreviousSound: function(){ 
     //this function is called when the user presses the back button in the controls.  
     //firstly disable back button 
     this.setState({backButtonIsDisabled: true}); 

     var currentIndex = this.state.currentSoundIndex; 
     var currentTime = this.player.currentTime; 
     //stop the player. this will set the currentTime to 0 also. 
     this.stopPlayer(); 
     //navigate to prev sound 
    }, 
    goToNextSound: function(){ 
     //this function is called when the user presses the forward button in the controls. 
     //firstly disable forward button 
     this.setState({forwardButtonIsDisabled: true}); 
     this.stopPlayer(); 

     //it sets the currentIndex to be the next index 
     var sounds = this.props.route.sounds[0]; //make a copy of the state of the sounds 
     var currentIndex = this.state.currentSoundIndex; 

     //navigate to next sound 
    }, 
    addToFavorites: function (i){ 
     var sounds = this.props.route.sounds[0]; 
     var selectedSound = sounds[i]; 
     this.props.favorites.push(selectedSound); 
     console.log("fav"); 
    }, 
    render: function() {  
     return(<div><div id="main-container" className={this.state.sidePanelIsOpen === true ? 'swipe-left' : ''}> 
       <div className="overlay"> 
        <Header toggleSidePanel={this.toggleSidePanel} sidePanelIsOpen={this.state.sidePanelIsOpen} /> 
        <div className="content"> 
         {this.props.children} 
        </div> 
        <AudioPlayer sounds={this.props.route.sounds[0]} currentSoundIndex={this.state.currentSoundIndex} /> 
        <Controls currentWidth={this.state.currentWidthOfTimerBar} currentTime={this.state.currentTime} sounds={this.props.route.sounds[0]} currentSoundIndex={this.state.currentSoundIndex} backButtonIsDisabled={this.state.backButtonIsDisabled} playButtonIsDisabled={this.state.playButtonIsDisabled} 
         forwardButtonIsDisabled={this.state.forwardButtonIsDisabled} 
         isPlaying={this.state.isPlaying} playPauseSound={this.playPauseSound} 
         goBack={this.goToPreviousSound} goForward={this.goToNextSound} /> 

       </div> 
      </div> 
      <div id="side-panel-area" class="scrollable">  
       <div class="side-panel-container"> 
        <div class="side-panel-header"><p>Menu</p></div> 

       </div> 
      </div></div> 
     ); 
    } 
}); 

var Home = React.createClass({ 
    render: function() { 
     return (
     <div> 
      <h2>Home</h2> 
      <p>This is the home component</p> 
     </div> 
    ); 
    } 
}); 


var Sounds = function(props) { 
    //this component will take in the currentSoundIndex state as a property and also the sounds array and the selectSound method 
    return (
     <div className="scrollable-container scrollable"> 
      <div id="list-of-sounds-container"> 

      <ul id="list-of-sounds"> 
      {props.sounds.map(function(sound, i) { 

       //this is the current sound playing so add a class called selected 
       return (
        <li className={"sound-list-item " + (props.currentSoundIndex === i ? 'selected' : 'not-selected')}> 
         <span className="sound-info-area" onClick={props.selectSound.bind(null, i)}> 
          <span className="sound-title">{sound.title}</span> 
          <span className="sound-artist">{sound.artist}</span> 
         </span> 

        </li> 
       ); 

      })} 
      </ul> 
      </div> 
     </div> 
    ); 
} 

var Controls = function(props) { 
    //this is a stateless component for the controls-area of the audio player. 
    //This area is fixed to the bottom of the screen and it contains the Display component, the TimerBar component 
    //and the controls of the Player i.e back, play/pause and forward. 
    return (<div id="controls-area"> 
      <div className="overlay"> 
       <Display sounds={props.sounds} currentSoundIndex={props.currentSoundIndex} /> 
       <TimerBar currentWidth={props.currentWidth} currentTime={props.currentTime} sounds={props.sounds} currentSoundIndex={props.currentSoundIndex}/> 
       <div id="controls"> 
        <button onClick={props.goBack} className="btn-control"><i className="fa fa-backward"></i></button> 
        <button onClick={props.playPauseSound} className="btn-control" disabled={props.playButtonIsDisabled}><i className={"fa " + (props.isPlaying ? 'fa-pause' : 'fa-play')}></i></button> 
        <button onClick={props.goForward} className="btn-control" disabled={props.forwardButtonIsDisabled}><i className="fa fa-forward"></i></button> 
       </div> 
      </div> 
     </div>  
     ); 
} 


ReactDOM.render(<Router history={hashHistory}> 
     <Route path="/" component={Application} sounds={soundsData} > 
      <IndexRoute component={Home} /> 
      <Route path="songs" component={Sounds} selectSound={this.selectSound} sounds={this.props.route.sounds[0]} currentSoundIndex={this.state.currentSoundIndex}/> 

      </Route> 
    </Router> 

, 
document.getElementById('application') 
); 
+1

あなたは 'state'または' methods'を渡さないで 'props'を渡します。 'redux'を使って作業するときには、reduxに接続する各コンポーネントがそれにアクセスでき、' state'という反応を持つ2種類の "状態"を持っています。これは各Reactコンポーネントのプライベートなローカル状態です。メソッドや新しい値を渡したいのですか?あなたはそれを受け取るコンポーネントが新しいデータを扱う責任があります(例えばそれをレンダリングします) –

答えて

1

あなたはここにいくつかのオプションがあります:

1.小道具でchildrenのクローンをレンダリングし、あなたのアプリケーションコンポーネントでthis.props.childrenをレンダリングするのではなく、このアプローチではcloneElement

を使用して、使用して送信したいReact.cloneElement

render: function() { 
    var clonedChildren = React.cloneElement(this.props.children,{currentSoundIndex: this.state.currentSoundIndex, selectSound: this.selectSound}); 
    return(
    //... 
    <div className="content"> 
    {clonedChildren} 
    //... 
); 
} 

2.リアクションコンテキストの使用

リアクションコンテキストを使用すると、トップレベルコンポーネントから下位レベルコンポーネントに任意の値を渡すことができます。しかし、このアプローチはほとんどの場合推奨されていません。文脈の仕組みの理解を深めるためdocsを読んでください。これは私がお勧めするものであるReduxの州立のような管理ライブラリ

を使用して

3。状態を複数のコンポーネント(この場合はアプリケーションとソング)と共有する必要があるため、Reduxのようなライブラリを使用して状態をグローバルに保ちます。次に、状態やメソッドの値を小道具としてコンポーネントに簡単に注入できます。

+0

ありがとうございます。私はここで最初のオプションを試してみましたが、完璧ですが、3番目のオプション(Reduxを使用)は、アプリが大きくなるにつれて特に優れていると私は同意します。私はReduxを使うようにもっと組織されていると思いますので、今私はそれを組み込む方法を学びます。ありがとうございました。 – Sarah

+0

私はあなたのために働いてうれしいです。 –

2

私は(私は専門家ではないが、多くの多くのミスの前に、私はより良いものを行う方法を実現することができます)反応し

を学び、あなたはReduxのを使用している場合ええ優れていたのと同じ問題を抱えていますあなたのアプリのために、この方法であなたは州のエラーを避けるでしょう、this例はあなたがreduxの仕組みを理解するのに役立ちます。

今私はthisを使用して自分のプロジェクトを整理しています。多分それはあなたにも合うかもしれません。

+0

あなたの役に立つリンクをありがとう。私はReduxを使用するつもりです:) – Sarah

+0

偉大な、この驚くべき世界にようこそ;)。 –

関連する問題