在使用 React Router 时如何将方法和状态传递给嵌套组件?
How can I pass in methods and states to a nested component while using React Router?
我已经学习 React 2-3 周了。我有几年 JavaScript.
的经验
我正在使用 React 制作一个 mp3 播放器,以便习惯应用程序的架构通常如何使用 React 组件工作。
我已经成功地让 mp3 与几个简单的组件一起工作。
现在我决定扩展应用程序,我想开发几个网页。
所以我开始学习如何使用 React Router 来导航到不同的 "pages"。
但是,在路由时将状态和方法从父组件传递到子组件时,我遇到了一个问题。
Application 组件包含 Header、AudioPlayer 和 Controls 组件,因为这些组件将成为应用程序每个页面的一部分。 (注意:控件组件由后退、前进、播放和暂停按钮以及显示当前音频进度的计时器栏组成。)
当路线 "path" 为 "songs" 时,我会将 "Sounds" 组件插入视图。
Sounds 组件需要接收在 Application 组件中定义的 selectSound 方法和 currentSoundIndex 状态。
有什么办法可以实现吗?
我也想遵循 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 Whosebug 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')
);
我在学习react时遇到了同样的问题(我不是专家但是在很多错误之前我可以意识到如何做得更好)
是的,如果您的应用程序使用 redux 会更好,这样您将避免状态错误,this 示例将帮助您了解 redux 的工作原理。
现在我正在使用 this 来组织我的项目,也许它也适合你。
您在这里没有几个选择:
1.使用 cloneElement
在这种方法中,不是在您的应用程序组件中渲染 this.props.children
,而是使用 React.cloneElement
渲染 children
的克隆以及您要发送的道具
render: function () {
var clonedChildren = React.cloneElement(this.props.children,{currentSoundIndex: this.state.currentSoundIndex, selectSound: this.selectSound});
return(
//...
<div className="content">
{clonedChildren}
//...
);
}
2。使用 React 上下文
您可以使用 React 上下文将任何值从顶级组件向下传递到较低级别的组件。但大多数情况下不推荐使用这种方法。阅读 docs 以更深入地了解上下文的工作原理。
3。像状态管理库一样使用 Redux
这是我推荐的。由于您需要与多个组件(在本例中为应用程序和歌曲)共享您的状态,因此请使用像 Redux 这样的库来保持状态全局。然后,您可以轻松地将状态和方法中的值作为 props 注入组件。
我已经学习 React 2-3 周了。我有几年 JavaScript.
的经验我正在使用 React 制作一个 mp3 播放器,以便习惯应用程序的架构通常如何使用 React 组件工作。 我已经成功地让 mp3 与几个简单的组件一起工作。
现在我决定扩展应用程序,我想开发几个网页。 所以我开始学习如何使用 React Router 来导航到不同的 "pages"。 但是,在路由时将状态和方法从父组件传递到子组件时,我遇到了一个问题。
Application 组件包含 Header、AudioPlayer 和 Controls 组件,因为这些组件将成为应用程序每个页面的一部分。 (注意:控件组件由后退、前进、播放和暂停按钮以及显示当前音频进度的计时器栏组成。)
当路线 "path" 为 "songs" 时,我会将 "Sounds" 组件插入视图。 Sounds 组件需要接收在 Application 组件中定义的 selectSound 方法和 currentSoundIndex 状态。 有什么办法可以实现吗?
我也想遵循 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 Whosebug 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')
);
我在学习react时遇到了同样的问题(我不是专家但是在很多错误之前我可以意识到如何做得更好)
是的,如果您的应用程序使用 redux 会更好,这样您将避免状态错误,this 示例将帮助您了解 redux 的工作原理。
现在我正在使用 this 来组织我的项目,也许它也适合你。
您在这里没有几个选择:
1.使用 cloneElement
在这种方法中,不是在您的应用程序组件中渲染 this.props.children
,而是使用 React.cloneElement
children
的克隆以及您要发送的道具
render: function () {
var clonedChildren = React.cloneElement(this.props.children,{currentSoundIndex: this.state.currentSoundIndex, selectSound: this.selectSound});
return(
//...
<div className="content">
{clonedChildren}
//...
);
}
2。使用 React 上下文
您可以使用 React 上下文将任何值从顶级组件向下传递到较低级别的组件。但大多数情况下不推荐使用这种方法。阅读 docs 以更深入地了解上下文的工作原理。
3。像状态管理库一样使用 Redux
这是我推荐的。由于您需要与多个组件(在本例中为应用程序和歌曲)共享您的状态,因此请使用像 Redux 这样的库来保持状态全局。然后,您可以轻松地将状态和方法中的值作为 props 注入组件。