当从父组件状态传递时,React prop没有在正确的时间更新
react prop not updating at correct time when passed from parent component state
我在react中遇到了一个非常烦人的bug,我认为这是与调用不在我的组件render
方法中的方法有关。我认为我已经提供了太多的代码,这将是很难为任何人提供帮助,但让我试着解释我的问题。
我正在进行一个返回25项的API调用。当用户点击下一步时,它应该改变背景并播放与背景相关的歌曲。这是可行的,但是当用户到达列表的末尾时,我想回到第一首歌。背景图像返回,但播放的是错误的歌曲。然后,当用户再次点击时,问题自行纠正。
我认为问题是由于nextSong
方法中的这一行。
this.audio.src = this.state.songs[this.props.song + 1];
请参阅下面的完整代码。如果有必要,我可以提供一个github链接,在那里项目可以克隆,这可能更容易调试。
我知道SO不是为了修复别人的代码,但以防万一我错过了一些基本的反应知识,我想在这里问。
var App = React.createClass({
getInitialState: function() {
return {
data: [],
song: 0
};
},
nextSong: function() {
if(this.state.song === this.state.data.length) {
this.setState({song : 0});
}else{
this.setState({song : this.state.song += 1});
}
},
previousSong: function() {
if(this.state.song === 0) {
this.setState({song : this.state.data.length});
}else{
this.setState({song : this.state.song -= 1});
}
},
getData: function(){
$.ajax({
url: '...',
method: 'GET',
success: function(response){
this.setState({data: response.items});
}.bind(this)
})
},
render: function() {
return(
<div>
<BackgroundImage src={this.state.data} image={this.state.song} />
<Button src={this.state.data} song={this.state.song} nextSong={this.nextSong} previousSong={this.previousSong} />
</div>
)
}
});
var BackgroundImage = React.createClass({
render: function() {
var images = this.props.src.map(function(photo, i){
return(photo.track.album.images[1].url)
})
var divStyle = {
background: 'url(' + images[this.props.image] + ')'
}
return(<div style={divStyle}></div>)
}
});
var Button = React.createClass({
getInitialState: function(){
return{
songs:[],
playing: false
}
},
audio: new Audio,
playSong: function(){
this.setState({playing: true});
this.audio.src = this.state.songs[this.props.song];
this.audio.play();
},
pauseSong: function() {
this.setState({playing: false});
this.audio.pause();
},
nextSong: function() {
this.audio.src = this.state.songs[this.props.song + 1];
this.audio.play();
},
onClickNext: function(){
this.setState({playing: true});
this.props.nextSong();
this.nextSong()
},
previousSong: function() {
this.setState({playing: true});
this.audio.src = this.state.songs[this.props.song - 1];
this.audio.play();
},
onClickPrevious: function(){
this.props.previousSong();
this.previousSong()
},
render: function() {
var self = this;
var songs = this.props.src.map(function(song, i){
self.state.songs.push(song.track.preview_url)
});
return (
<div className="button">
<div className="next" onClick={this.onClickNext}>
NEXT
</div>
<div className="pause">
<p onClick={this.state.playing ? this.pauseSong : this.playSong}></p>
</div>
<div className="prev" onClick={this.onClickPrevious}>
PREVIOUS
</div>
</div>
)
}
});
首先,你不应该这样做:
this.setState({song : this.state.song += 1});
因为那会首先设置this。state。song然后是setState,这会在你更新state而不通知react时产生一些冲突。我不确定react是否能正确处理。相反,只需这样做:
this.setState({song : this.state.song + 1});
(同-=
)
回到问题:你应该只在一个地方记录当前的歌曲。在这种情况下,你应该保持它在App组件的状态,而按钮组件不应该。像nextSong这样的方法在按钮组件中没有位置。相反,你可以在App组件中更新歌曲(就像你在它的nextSong方法中所做的那样),并通过props将当前歌曲直接传递给按钮组件。
然后,您可以使用生命周期方法componentWillReceiveProps
检测当前歌曲是否更改,如下所示:
componentWillReceiveProps: function(nextProps) {
this.playSong(nextProps.song);
}
所以,当你在App中改变状态时,它会更新并传递新的props给Button,然后Button会用新歌触发componentWillReceiveProps并做它的事情
您可以尝试使用componentWillUpdate(nextProps, nextState)
生命周期方法来获取可用的道具。像这样:
componentWillUpdate(nextProps, nextState) {
if (nextProps.song !=== this.props.song) {
this.audio.src = this.state.songs[nextProps.song];
this.audio.play();
}
}
同样,您不需要在previousSong()
和nextSong()
方法中使用this.audio.src=...
和this.audio.play()
部分。
我看了看你的代码在Github,我发现改变这些方法在<App />
组件看起来像:
nextSong: function() {
console.log(this.state.song)
if(this.state.song === this.state.data.length - 1) {
this.setState({song : 0});
}else{
this.setState({song : this.state.song + 1});
}
},
previousSong: function() {
if(this.state.song === 0) {
this.setState({song : this.state.data.length - 1});
}else{
this.setState({song : this.state.song - 1});
}
},
请注意,您必须使最后一个索引等于data.length - 1
,因为data.length
将是未定义的。
此外,我已经改变了这些方法在<Button />
组件以及:
onClickNext: function(){
this.setState({playing: true});
this.props.nextSong();
},
onClickPrevious: function(){
this.props.previousSong();
},
现在,它似乎在这里为我工作。
发生这种情况的原因是您在App
组件中正确更新了跟踪索引,但在Button
组件中没有。当到达最后一首歌时,从Button
调用this.props.nextSong()
, App
将把索引设置为0。但在此之后,还调用this.nextSong()
(来自Button
),它将播放索引为this.props.song + 1
的音轨中的音频。
这可能是你误解的地方。这里,this.props.song
仍然与onClickNext()
相同,所以它实际上会尝试播放一首不存在的歌曲(在您的示例中,它将尝试播放索引为25的歌曲,但只有0-24的歌曲)。
一个简单的解决方案是在你的Button
中使用相同的逻辑,即当你到达终点时检查,然后在索引0处播放歌曲。但是现在你要记录两次相同的状态,这真的不是一个好主意。
一个更好的解决方案是按照这个答案中建议的那样做,或者只是让App
播放音频。
- Html5音频时间更新精度
- 使用引导时间选取器时,没有更新数据ng模型值
- 更新 FLOT 时间序列图
- 如果在线检查期间发生电源故障,我们如何更新用户再次登录门户时的剩余时间
- JQuery:更新表内容花费的时间太长
- 如何制作倒计时计时器并在时间到时更新MySQL记录
- 如何在页面上显示时间、日期IP和URL?更新日期:2015年2月25日
- 如何在一个时间间隔后追加新的更新
- 在 PHP / Javascript 中实时更新时间
- Javascript/jQuery自动设置和更新时间
- Moment.js以秒为单位动态更新时间
- 我想用Meteor.setInterval()来更新时间模板,它不起作用
- jquery flot图表不会动态更新时间序列的X轴值
- angularjs应用程序中的更新时间
- 在日期时间字段中设置更新时间部分,但不更新日期部分
- 用Javascript更新时间元素
- 如何使Meteor响应式更新“时间since”;字符串
- 如何根据数组数量的增加来更新时间间隔的时间
- Javascript更新时间函数导致高CPU
- 在Mustache模板中使用setInterval更新时间