72

我使用带有大量项目的 FlatList。我收到来自 Expo XDE 的以下警报。

VirtualizedList:您有一个更新缓慢的大型列表 - 确保您的 renderItem 函数呈现遵循 React 性能最佳实践的组件,如 PureComponent、shouldComponentUpdate 等。 {"dt":13861,"prevDt":1498372326027,"contentLength":第6624章

我对我的 FlatList 使用了一些优化方法,例如 PureComponent,但我仍然收到此警报。在我描述我的优化之前,你能告诉我即使 FlatList 已经优化,这个警报是否总是出现?或者它可能表明性能存在实际问题?我问是因为我的 FlatList 的性能很好。

4

7 回答 7

83

我以前看到过这个错误。优化我的代码后,我不再看到它。我通过将 console.log() 语句添加到创建 FlatList 的组件的 render() 函数以及呈现列表中每个项目的函数来解决问题。我注意到,每当该页面上的任何组件(甚至是与 FlatList 无关的组件)发生状态更改时,我的代码都会重新渲染整个 FlatList 及其所有项目。我通过将各种组件转换为 PureComponents 来解决这个问题。这是我的 FlatList 声明的样子:

<FlatList
    ref={(ref) => { this.flatListRef = ref; }}
    data={allPosts}
    initialNumToRender={7}
    renderItem={({ item }) =>
      <Post postJson={item} isGroupAdmin={isGroupAdmin} user={user} />
    }
  />

请注意,我返回<Post />的是一个纯组件:

import React, { PureComponent } from 'react';
class Post extends PureComponent {

  render() { ... }
}

这确保了 FlatList 仅在帖子更改时才重新呈现 a。当我之前将一个普通函数传递给renderItemie 时,一个执行以下操作的函数:

return (
  <View>
  ...
  </View>
);

我注意到,每当任何项目发生变化时,FlatList 都会重新渲染所有项目。现在,通过使用 PureComponent,FlatList 只呈现添加到列表中的新项目(如果列表已经显示)。

第一次渲染整个列表仍然需要相对较长的时间。但是,initialNumToRender确保屏幕几乎立即被填满(而剩余的项目在后台呈现)。更重要的是,在初始渲染之后,FlatList 只需一次渲染一个项目(更改的项目)。

我发现这篇文章很有帮助)。

我刚刚意识到这也在这里解释

于 2017-09-21T16:44:48.420 回答
16

我注意到这个问题的答案并没有为那些使用功能组件和钩子的人提供解决方案。我遇到了这个问题,我可以通过使用钩子“useMemo()”来摆脱它

<FlatList
                keyExtractor={keyExtractor}
                data={productsState.products}
                renderItem={renderItem}
            />
const renderItem = ({ item }) => (
            <ListItem
                title={item.ProductName}
                subtitle={(item.ProductQuantity) + " " + (item.QuantityType !== 
                null ? item.QuantityType : " ") }
                bottomDivider
                topDivider
                chevron
                checkmark={checkMark}
                onLongPress={() => setCheckMark(!checkMark)}
                rightSubtitle={(item.Currency !== null ? item.Currency: " " ) + 
                " " + (item.productCost !== null ? item.productCost: " " )}
                rightSubtitleStyle={{ marginTop: -20 }}
                badge={{ value: item.sellingPrice, textStyle: { color: 'orange' }, containerStyle: { marginTop: -20 } }}
            />
        )

renderItem 函数是一项昂贵的计算,因为它要渲染的列表很长。相反,我将其记忆如下

            const memoizedValue = useMemo(() => renderItem, [productsState.product]);

<FlatList
                keyExtractor={keyExtractor}
                data={productsState.products}
                renderItem={memoizedValue}
            />
const renderItem = ({ item }) => (
        <ListItem
            title={item.ProductName}
            subtitle={(item.ProductQuantity) + " " + (item.QuantityType !== 
            null ? item.QuantityType : " ") }
            bottomDivider
            topDivider
            chevron
            checkmark={checkMark}
            onLongPress={() => setCheckMark(!checkMark)}
            rightSubtitle={(item.Currency !== null ? item.Currency: " " ) + 
            " " + (item.productCost !== null ? item.productCost: " " )}
            rightSubtitleStyle={{ marginTop: -20 }}
            badge={{ value: item.sellingPrice, textStyle: { color: 'orange' }, containerStyle: { marginTop: -20 } }}
        />
    )

不要忘记从 react 中导入 useMemo,以使其正常工作。

祝你好运!

于 2020-08-10T20:21:07.510 回答
5

我想通了,为什么会发生这个错误。主要问题是,当你的 onEndReached 事件发生时,我确定你正在从服务器加载一些东西,这意味着你需要等到你从服务器加载完成,所以之后你可以调用 onEndReached 事件。

但在您的情况下,有多个 onEndReached 事件调用。因此,当它发生时,您的应用程序会一次又一次地尝试从服务器加载数据。

好的,如何解决这个问题:你需要创建新的状态,例如这是通过分页实现无限滚动。

const [loader, setLoader] = useState<boolean>(false);

const onEndReached = (page) => {
  if (next && !loader) {
    setPage(page + 1)
  }
}

const loadData = async () => {
  setLoader(true);
  const resp = await getData();
  setLoader(false);
}

<FlatList ...someprops onEndReached={onEndReached} />
于 2020-08-12T10:58:15.620 回答
4

如果您使用的是函数式组件,则将组件包装在其中memo是一种防止不必要的渲染的好方法,而无需经历将函数式组件转换为纯类组件的麻烦。这篇文章解释了更多

按照这个例子:

在父组件中:

import React from 'react';
import {FlatList} from 'react-native';
import PostCard from './PostCard';

export const NewsFeeds = props => {
      return (
        <FlatList
          data={data}
          initialNumToRender={4}
          refreshing={loading}
          renderItem={_renderitem}
        />
      );
    };

const _renderitem = ({item}) => <PostCard item={item} />;

在子组件中

import React, {memo} from 'react';
import {View} from 'react-native';

const PostCard = (props) => {
        return (
            <View>
    
            </View>
        );
    };
    
 export default memo(PostCard);

React. PureComponent如果您使用的是类组件,请通过扩展类定义来确保您的组件是纯组件

class NewsFeeds extends React.PureComponent {
  render() {
    return (
      <FlatList
          data={data}
          initialNumToRender={4}
          refreshing={loading}
          renderItem={_renderitem}
      />
    )
  }
}
于 2021-12-01T05:22:35.653 回答
4

添加这个道具:

initialNumToRender={n} 

为我工作(n数量相当少,例如5)。

于 2021-07-27T15:42:26.950 回答
3

还要确保不要用 SourceList 封装 FlatList。对我来说,它意外地出现了,因为我使用了 native-base,并且没有注意到它们的 Component<Content>替换了 ScrollList。

有关更多信息,请参见此处:https ://stackoverflow.com/a/54512633/1256697

于 2019-02-04T12:18:56.457 回答
1

除了给出的所有答案之外,您还可以尝试设置removeClippedSubviewstrue.

<FlatList
  removeClippedSubviews

  // ...other props
/>

removeClippedSubviews当一个项目从视图中消失时,通过启用内存被释放。当您有一个长而复杂的列表(即卡片列表)时,每张卡片的 DOM 会变得非常大,因此最好在它不可见时释放内存。

此外,如果您结合使用useCallback()而不是useMemo()在“数据”更改时释放更多内存

const renderItem = useCallback(originalRenderItem, [data])

useMemo()方法将根据值进行记忆,但当数据更改时它应该真正释放自己。通过这样做useCallback(),您将获得使用“函数作为参数”的好处,因此您不需要

const renderItem = useCallback(({item, index} 
  => originalRenderItem({item, index}), [data])

从而使它看起来像一个包装函数,而没有太多的阅读给下一个人。

做到这两个:

  • 防止调用render()最近更新的组件的潜在昂贵功能。
  • 减少不可见组件使用的内存
  • data如果更改较早,则释放记忆的数据。
于 2021-03-17T09:08:53.937 回答