React 虚拟化列表 运行 低于 60fps。如何优化?

React-virtualized list running below 60fps. How to optimize?

我正在尝试创建一个 post 供稿,就像一个 instagram(在主页上)那样。

我正在使用 Infinite-loader 进行抓取,Window-scroller 用于使用 window 作为滚动条,auto-sizer 用于按我想要的方式调整列表大小,CellMeasurer 用于测量 'post component' 图片加载完成后一次。

这是列表组件的代码:

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>
    );
}

这里是 post 组件的代码:

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>
    )

即使我从 post 组件中删除所有内容并只保留图像,它仍然不会 运行 超过 45-50fps 有时也会低于 40fps。

我可以以任何方式优化我的方法还是我做错了什么? 我是否应该提供其他可能有用的信息?

提前致谢!

所以我通过在上传图像时调整图像大小解决了我的问题(在后端使用 sharp)。

这种方式的获取速度更快 使得 post 组件的(及时)测量更快,因为挂载时需要加载的数据更少,并且 html +css 不必将高图像调整到较小的容器中。

听起来很傻,但我不认为这是问题所在,而是专注于我的无限滚动实现 :D

你生活你学习

编辑:

我忘了说了,我在设置图像 src 时做了一个小改动。 我可以在接收 post 信息时使用它,而不是创建一个检索 src 的快速路由。文件源不会用 console.log 或其他任何方式打印,但它在那里并且可以像这样使用:

<Image className={styles.image} onLoad={props.measure} src={`data:${props.post.source.contentType};base64,${Buffer.from(props.post.source.data).toString('base64')}`} />