簡體   English   中英

反應子更新更改父滾動位置

[英]React child updates change parent scroll position

我有一個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>
    );
};

我意識到問題更多是css,而不是react代碼。 我沒有為容器設置明確的高度,並且我認為正是這種不確定性導致瀏覽器在列表元素自己重新渲染/更新時突然在容器中上下滾動。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM