当从父组件状态传递时,React prop没有在正确的时间更新

react prop not updating at correct time when passed from parent component state

本文关键字:更新 时间 prop React 组件 状态      更新时间:2023-09-26

我在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播放音频。