反应子更新更改父滚动位置

时间:2018-07-17 04:49:06

标签: javascript reactjs scroll

我有一个React容器组件,该组件应该容纳一个子计时器组件列表。这些子组件只是倒数计时器。

为此,我的子组件类使用setInterval每秒更新一次子组件。随着更新的进行,我注意到在向下或向上滚动列表时,恰好在子计时器更新自身时,容器组件会突然以明显大的方式突然向上或向下跳跃,但是从未调用容器的render方法。

与此同时,如果我只是不滚动或停止滚动,则跳跃永远不会发生。如果我滚动然后停止滚动,即使我停止滚动,onscroll处理程序也会与子计时器更新保持同步触发。

我以前从未遇到过这样的情况。 React子代可以在更新时隐式强制其父容器随机上下滚动吗?

这是容器的代码:

class UserContentDetail extends React.Component {
    constructor(props) {
        super(props);
        this.state ={
            page: 1,
            perPage: 15,
            animes: [],
            currTab: props.currTab
        };

        this.startApiRequests = this.startApiRequests.bind(this);
        this.handleResponse = this.handleResponse.bind(this);
        this.handleData = this.handleData.bind(this);
        this.handleError = this.handleError.bind(this);
    }

    componentDidMount() {
        this.startApiRequests();
    }

    componentDidUpdate() {
        if (this.props.currTab !== this.state.currTab) {
            this.startApiRequests();
        }
    }

    startApiRequests() {
        let currSeason = anilistApiConstants.SEASON_SUMMER;
        let currSeasonYear = 2018;

        let resultPromise = null;
        switch(this.props.currTab) {
            case sideNavConstants.SIDE_NAV_TAB_MY_ANIME:
                resultPromise = api.getMyAnimes(this.props.myAnimeIds, this.state.page, this.state.perPage);
                break;
            case sideNavConstants.SIDE_NAV_TAB_POPULAR_ANIME:
                resultPromise = api.getPopularAnimes(this.state.page, this.state.perPage);
                break;
            case sideNavConstants.SIDE_NAV_TAB_NEW_ANIME:
                resultPromise = api.getNewAnimes(currSeason, currSeasonYear, this.state.page, this.state.perPage);
                break;
        }

        resultPromise.then(this.handleResponse)
            .then(this.handleData)
            .catch(this.handleError);
    }

    handleResponse(response) {
        return response.json().then(function (json) {
            return response.ok ? json : Promise.reject(json);
        });
    }

    handleData(data) {
        let results = data.data.Page.media;
        for (let i = 0; i < results.length; ++i) {
            if (results[i].nextAiringEpisode == null) {
                results[i].nextAiringEpisode = {empty: true};
            }
        }
        this.setState({
            page: 1,
            perPage: 15,
            animes: results,
            currTab: this.props.currTab
        });
    }

    handleError(error) {
        alert('Error, check console');
        console.error(error);
    }

    render() {
        console.log('rendering list');
        return(
            <div className={userMasterDetailStyles.detailWrapper}>
                <div className={userMasterDetailStyles.detailList}>
                    {this.state.animes.map(anime => <AnimeCard {...anime} key={anime.id} />)}
                </div>
            </div>
        );
    }
}

这是我的计时器(AnimeCardTime)的代码,它被卡容器(AnimeCard)包围:

class AnimeCardTime extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            timeUntilNextEpisode: props.timeLeft,
            animeId: props.id
        };

        this.countdownTimer = null;

        this.getNextEpisodeTimeUntilString = this.getNextEpisodeTimeUntilString.bind(this);
        this.startTimer = this.startTimer.bind(this);
        this.endTimer = this.endTimer.bind(this);
    }

    componentDidMount() {
        this.startTimer();
    }

    componentWillUnmount() {
        this.endTimer();
    }

    static getDerivedStateFromProps(nextProps, prevState) {
        if (nextProps.id !== prevState.animeId) {
            return {
                timeUntilNextEpisode: nextProps.timeLeft,
                animeId: nextProps.id
            };
        }
        return null;
    }

    startTimer() {
        if (this.props.timeLeft != undefined) {
            this.countdownTimer = setInterval(() => {
                this.setState({
                    timeUntilNextEpisode: this.state.timeUntilNextEpisode - 60,
                });
            }, 1000);
        }
    }

    endTimer() {
        if (this.countdownTimer != null) {
            clearInterval(this.countdownTimer);
        }
    }

    secondsToTimeString(timeSecondsUntil) {
        timeSecondsUntil = Number(timeSecondsUntil);
        let d = Math.floor(timeSecondsUntil / (3600*24));
        let h = Math.floor(timeSecondsUntil % (3600*24) / 3600);
        let m = Math.floor(timeSecondsUntil % (3600*24) % 3600 / 60);

        let dDisplay = d > 0 ? d + 'd ' : '';
        let hDisplay = h > 0 ? h + 'hr ' : '';
        let mDisplay = m > 0 ? m + 'm ' : '';
        return dDisplay + hDisplay + mDisplay; 
    }

    getNextEpisodeTimeUntilString() {
        if (this.props.timeLeft != undefined) {
            return 'Ep ' + this.props.nextEpisode + ' - ' + this.secondsToTimeString(this.state.timeUntilNextEpisode);
        }
        else {
            return this.props.season + ' ' + this.props.seasonYear;
        }
    }

    render() {
        return(<h6 className={userMasterDetailStyles.cardTime}>{this.getNextEpisodeTimeUntilString()}</h6>);
    }
}

const AnimeCard = (props) => {

    let secondsToTimeString = (timeSecondsUntil) => {
        timeSecondsUntil = Number(timeSecondsUntil);
        let d = Math.floor(timeSecondsUntil / (3600*24));
        let h = Math.floor(timeSecondsUntil % (3600*24) / 3600);
        let m = Math.floor(timeSecondsUntil % (3600*24) % 3600 / 60);

        let dDisplay = d > 0 ? d + 'd ' : '';
        let hDisplay = h > 0 ? h + 'hr ' : '';
        let mDisplay = m > 0 ? m + 'm ' : '';
        return dDisplay + hDisplay + mDisplay; 
    };

    let getNextEpisodeTimeUntilString = () => {
        if (props.status === anilistApiConstants.STATUS_RELEASING) {
            return 'Ep ' + props.nextAiringEpisode.episode + ' - ' + secondsToTimeString(props.nextAiringEpisode.timeUntilAiring);
        }
        else {
            return props.season + ' ' + props.startDate.year;
        }
    };

    return(
        /* <h6 className={userMasterDetailStyles.cardTime}>{getNextEpisodeTimeUntilString()}</h6> */
        <a className={userMasterDetailStyles.animeCardLinkContainer} href={props.siteUrl}>
            <div className={userMasterDetailStyles.animeCardContainer}>
                <h6 className={userMasterDetailStyles.cardTitle}>{props.title.romaji}</h6>
                <AnimeCardTime timeLeft={props.nextAiringEpisode.timeUntilAiring} nextEpisode={props.nextAiringEpisode.episode} season={props.season} seasonYear={props.startDate.year} id={props.id}/>
                <img className={userMasterDetailStyles.cardImage} src={props.coverImage.large}/>
                <p className={userMasterDetailStyles.cardDescription}>{props.description.replace(/<(?:.|\n)*?>/gm, '')}</p>
                <p className={userMasterDetailStyles.cardGenres}>{props.genres.reduce((prev, curr) => {return prev + ', ' + curr;})}</p>
            </div>
        </a>
    );
};

1 个答案:

答案 0 :(得分:0)

我意识到问题更多是css而不是react代码。我没有为容器设置明确的高度,并且我认为正是这种不确定性导致浏览器在列表元素自己重新渲染/更新时突然在容器中上下滚动。