繁体   English   中英

使用React Router时如何将方法和状态传递给嵌套组件?

[英]How can I pass in methods and states to a nested component while using React Router?

我已经学习反应了2-3周。 我有几年使用JavaScript的经验。

我正在使用React制作一个mp3播放器,以习惯于应用程序的体系结构通常使用React组件来工作。 我已经成功地使mp3与一些简单的组件一起工作。

现在,我决定扩展该应用程序,并想开发几个网页。 因此,我开始学习如何使用React Router来导航到不同的“页面”。 但是,在路由时将状态和方法从父组件传递到子组件时遇到了一个问题。

Application组件包含Header,AudioPlayer和Controls组件,因为这些组件将成为应用程序每个页面的一部分。 (注意:“控件”组件由“上一步”,“前进”,“播放”和“暂停”按钮以及用于显示当前音频进度的计时器栏组成。)

当路线“路径”是“歌曲”时,我将在视图中插入“声音”组件。 Sounds组件需要采用selectSound方法,并且currentSoundIndex状态都需要在Application组件中定义。 有没有办法可以做到这一点?

我也想遵循React的最佳实践,所以如果有更好的方法来组织这个应用程序,我想知道。 (例如,我应该使用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')
);

我在学习反应时遇到了同样的问题(我不是专家,但在遇到很多错误之前,我已经意识到如何做得更好)

是的,如果您为应用程序使用redux更好,这样可以避免状态错误, 示例将帮助您了解redux的工作方式。

现在,我正在使用来组织我的项目,也许它也适合您。

您在这里有几个选择:

1.使用cloneElement

在这种方法中,而不是渲染this.props.children在你的应用程序组件,使克隆children与道具你想用送React.cloneElement

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

2.使用React上下文

您可以使用React上下文将任何值从顶级组件传递到更低级别的组件。 但是,在大多数情况下,不建议使用此方法。 阅读文档以获得对上下文如何工作的更多了解。

3.使用像状态管理库这样的Redux

这就是我的建议。 由于您需要与多个组件(在本例中为Application和Songs)共享状态,因此请使用Redux之类的库来使状态保持全局。 然后,您可以轻松地将状态和方法中的值作为prop注入到组件中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM