[英]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')
);
您在这里有几个选择:
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.