Реагирующий виртуализированный список со скоростью ниже 60 кадров в секунду. Как оптимизировать? - PullRequest
0 голосов

Я пытаюсь создать фид сообщений, подобный тому, который есть в instagram (на главной странице).

Я использую Infinite-loader для загрузки, Window-scroller для использования окна в качестве прокрутки, автосайзер для изменения размера списка, как я хочу, и CellMeasurer для измерения «компонента сообщения» один раз после загрузки изображения.

Вот код для компонента списка:

class PostsPartial extends React.PureComponent<IProps>{
state: IPostsPartialState = { posts: [], hasMorePosts: true }

private cache: CellMeasurerCache;

private get rowCount(): number {
    return this.state.hasMorePosts ? this.state.posts.length + 1 : this.state.posts.length;
}

constructor(props: IProps) {
    super(props);

    this.cache = new CellMeasurerCache({
        fixedWidth: true,
        defaultHeight: 1000
    });

    this.renderRow = this.renderRow.bind(this);
}

private fetchPosts = ({ startIndex, stopIndex }: { startIndex: number, stopIndex: number }) => {
    return getNewPostsChunk(startIndex, stopIndex - startIndex, this.props.token).then((res: IPostsChunkResponse) => {
        if (res.success) {
            if (res.posts.length === 0) {
                // no more posts
                this.setState({ hasMorePosts: false })
            }
            else {
                let newPosts = [...this.state.posts, ...res.posts];
                this.setState({ posts: newPosts })
            }
        }
        else {
            // internal error
        }
    })
};

private renderRow({ index, key, parent, style }: any) {
    return (
        <CellMeasurer
            cache={this.cache}
            columnIndex={0}
            key={key}
            parent={parent}
            rowIndex={index}
        >
            {({ measure, registerChild }: any) => (
                <div className={styles.paddingContainer} ref={registerChild} style={style}>
                    <Post
                        isLoaded={this.isRowLoaded({index})}
                        measure={measure}
                        post={this.state.posts[index]}
                    />
                </div>
            )}
        </CellMeasurer>
    );
}

private isRowLoaded = ({ index }: { index: number }) => {
    return !!this.state.posts[index];
};

public render() {
    return (
        <div className={styles.mainContainer}>
            <InfiniteLoader
                isRowLoaded={this.isRowLoaded}
                loadMoreRows={this.fetchPosts}
                rowCount={this.rowCount}
            >
                {({ onRowsRendered, registerChild }: InfiniteLoaderChildProps) => (
                    <WindowScroller>
                        {({ height, isScrolling, onChildScroll, scrollTop }) => (
                            <AutoSizer disableHeight>
                                {
                                    ({ width }: any) => (
                                        <List
                                            ref={registerChild}
                                            onRowsRendered={onRowsRendered}
                                            autoHeight
                                            width={width}
                                            height={height}
                                            isScrolling={isScrolling}
                                            onScroll={onChildScroll}
                                            scrollTop={scrollTop}
                                            deferredMeasurementCache={this.cache}
                                            rowHeight={this.cache.rowHeight}
                                            rowRenderer={this.renderRow}
                                            rowCount={this.rowCount}
                                            overscanRowCount={10}
                                        />
                                    )
                                }
                            </AutoSizer>
                        )}
                    </WindowScroller>
                )}
            </InfiniteLoader>
        </div>
    );
}

и вот код для компонента сообщения:

const Post:React.FC<IProps> = (props:IProps) => {
    if(props.post && props.isLoaded)
    return (
        <div className={styles.container}>
            <Segment className={styles.profileSegmentInternal} attached='top'>
                <Image className={styles.verySmallImg} circular size='tiny' src={`${settings.BASE_URL}/feed/photo/user/${props.post.creator}`}></Image>
                <Link to={`/profile/${props.post.creator}`}>
                    <Header size='small' className={styles.headerName} as='span'>{props.post.creator}</Header>
                </Link>
            </Segment>
            <div className={styles.imageContainer}>
                <Image onLoad={props.measure} src={`${settings.BASE_URL}/feed/photo/post/${props.post._id}`} className={styles.image}></Image>
            </div>
            
            <Segment className={styles.bottomSegment} attached='bottom'>
                <>
                    <Menu className={styles.postMenu}>
                        <Item className='left'>
                            <Icon className={styles.iconBtn} size='big' name='heart outline'></Icon>
                            <Icon className={styles.iconBtn} size='big' name='comment outline'></Icon>
                            <Icon className={styles.iconBtn} size='big' name='paper plane outline'></Icon>
                        </Item>
                        <Item className='right'>
                            <Icon className={styles.iconBtn} size='big' name='bookmark outline'></Icon>
                        </Item>
                    </Menu>
                </>
                <Header className={styles.likes} size='tiny'>{props.post.likesCount} likes</Header>
                <Header className={styles.description} size='tiny'>
                    <Header size='tiny' className={styles.commentUsername} as='span'>{props.post.creator}</Header>
                    <Header className={styles.commentText} as='span' size='tiny'> {props.post.description}</Header>
                </Header>
                <Link to='#'>
                    <Header className={styles.viewAllComments} size='tiny' disabled>View all comments</Header>
                </Link>
                {
                    //backend will return the first 3-4 messeges only
                    // props.post.messeges.map((messege,index) => (

                    // ))
                }
                <Form className={styles.commentForm}>
                    <Form.Field className={styles.commentField}>
                        <Form.Input
                            className={styles.commentInput}
                            placeholder='Adding comment ...'
                        >

                        </Form.Input>
                        <Button className={styles.commentSubmit} size='medium' primary>Comment</Button>
                    </Form.Field>
                </Form>
            </Segment>
        </div>
    )
    else
    return (
        <p>loading</p>
    )

Даже если я удалю все из компонента сообщения и оставлю только изображение, он все равно не будет работать со скоростью более 45-50 кадров в секунду, иногда даже ниже 40 кадров в секунду .

Могу ли я как-то оптимизировать свой подход или я что-то делаю не так? Могу ли я предоставить что-нибудь еще, что может быть полезно?

Заранее благодарю!

1 Ответ

0 голосов

Итак, я исправил свою проблему, изменив размер изображения при его загрузке (в бэкэнде, используя резкость ).

Таким образом, загрузка выполняется быстрее, ведьма (как раз вовремя) пост-компонента намного быстрее, так как при монтировании нужно загружать меньше данных, а html + css не должен изменять размер высокого изображения в меньший контейнер.

Звучит глупо, но я не думал из-за этой проблемы и вместо этого сосредоточился на моей реализации бесконечной прокрутки: D

Я живу, и я учусь

РЕДАКТИРОВАТЬ:

Я забыл упомянуть, небольшое изменение, которое я сделал при установке изображения sr c. Вместо того, чтобы создавать маршрут express, который извлекает sr c, я могу просто использовать его при получении информации о посте. Исходный файл не будет напечатан с console.log или чем-то еще, но он есть и может использоваться следующим образом:

<Image className={styles.image} onLoad={props.measure} src={`data:${props.post.source.contentType};base64,${Buffer.from(props.post.source.data).toString('base64')}`} />
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...