【问题标题】:VirtualizedList: You have a large list that is slow to updateVirtualizedList:您有一个更新缓慢的大型列表
【发布时间】:2017-11-28 08:38:38
【问题描述】:

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

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

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

【问题讨论】:

  • 我经常在开发模式下得到这个,我认为是因为在这种情况下正在进行的所有其他检查。
  • 嗨 raarts,感谢您的评论,实际上,当我不在 Expo 的开发模式下时,我也明白了。
  • 你能解决这个问题吗?

标签: reactjs react-native react-native-flatlist


【解决方案1】:

我之前看到过这个错误。优化我的代码后,我不再看到它。我通过将 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} />
    }
  />

请注意,我正在返回 &lt;Post /&gt;,这是一个纯组件:

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

  render() { ... }
}

这可确保 FlatList 仅在帖子更改时重新呈现 a。当我之前将普通函数传递给 renderItem 时,即执行如下操作的函数:

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

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

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

我发现this post 很有帮助)。

我刚刚意识到这也解释了here

【讨论】:

  • 我不确定我的问题是不是这个
  • 我正在使用 PureComponent 但它不会为我隐藏警告,仍然会收到“您的列表很大,更新速度很慢”警告
  • 是否可以在功能组件中解决这个问题
  • 在功能组件中,您需要用 React.memo 包装您的列表项组件。您还需要确保仅将前置道具发送给孩子。如果您还发送函数/对象/数组等,则需要使用 useMemo 或 useCallback 来记忆它们。
【解决方案2】:

我注意到这个问题的答案并没有为那些使用功能组件和钩子的人提供解决方案。我遇到了这个问题,我可以通过使用钩子“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,以使其正常工作。

祝你好运!

【讨论】:

  • 你也可以看看this,你知道吗?
  • 我刚刚回答了
  • 所有的记忆项都依赖于同一个状态。当productsState.product 更改时,这不会使每个项目都重新呈现吗?
  • 是的,它只会在 productsState.product 时重新渲染
  • 仍然收到同样的警告
【解决方案3】:

我想通了,为什么会发生这个错误。主要问题是,当您的 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} />

【讨论】:

    【解决方案4】:

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

    更多信息请看这里:https://stackoverflow.com/a/54512633/1256697

    【讨论】:

      【解决方案5】:

      添加这个道具:

      initialNumToRender={n} 
      

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

      【讨论】:

      • 我建议在跳到其他答案中的其他复杂解决方案之前使用此解决方案。谢谢
      【解决方案6】:

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

      按照这个例子:

      在父组件中:

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

      【讨论】:

        【解决方案7】:

        除了给出的所有答案之外,您还可以尝试将removeClippedSubviews 设置为true

        <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 更早更改,则释放记忆数据。

        【讨论】:

        • 你能提供一些关于它的信息吗?
        • 让我知道我是否遗漏了某些内容,但是将数据添加到 renderItem 的依赖项没有多大意义并且会造成伤害。只有当你想对同一个列表使用多个渲染函数变体时,你才会想要改变 renderItem
        猜你喜欢
        • 2021-12-06
        • 2023-01-24
        • 1970-01-01
        • 1970-01-01
        • 2011-04-07
        • 1970-01-01
        • 2021-12-13
        • 2021-07-03
        • 2014-10-08
        相关资源
        最近更新 更多