簡體   English   中英

“VirtualizedList:您有一個更新緩慢的大列表”警告,即使所有組件都已優化

[英]"VirtualizedList: You have a large list that is slow to update" warning even though all components are optimized

在使用帶有 React-Native 的<FlatList>組件時,試圖避免收到“VirtualizedList: You have a large list that is slow to update”警告時遇到了很多麻煩。

我已經做了大量的研究和 Google-Fu 來嘗試解決方案,但沒有一個解決方案有幫助,甚至沒有解決這個 GitHub 問題的解決方案。

要記住的事情:

  • 我正在使用shouldComponentUpdate() { return false; } shouldComponentUpdate() { return false; }在我的列表項組件中,所以它們根本沒有更新。
  • 渲染 FlatList 的組件已經是PureComponent
  • 我在組件的渲染方法中添加了 console.logs,並且可以確認它們不會重新渲染。
  • 我沒有使用匿名函數,因此每次調用都不會重新初始化renderItem組件。

重要提示:警告似乎僅在使用我的BottomTabNavigator切換到單獨的選項卡然后返回並滾動瀏覽我的列表后才會出現; 但這很令人困惑,因為當我這樣做時,FlatList 屏幕組件不會重新呈現,列表項也不會。 由於瀏覽到另一個選項卡時組件不會重新渲染,為什么會發生此錯誤?

這是我的確切代碼:

應用程序.tsx

const AppRoutes = [
  { name: "Home", Component: HomeScreen },
  { name: "Catalog", Component: CatalogScreen },
  { name: "Cart", Component: CartScreen }
];

export const StoreApp = () => {
  return (
    <NavigationContainer>
      <StatusBar barStyle="dark-content" />
      <Tabs.Navigator>
        {AppRoutes.map((route, index) => 
          <Tabs.Screen
            key={index}
            name={route.name}
            component={route.Component}
            options={{
              headerShown: (route.name !== "Home") ?? false,
              tabBarIcon: props => <TabIcon icon={route.name} {...props} />
            }}
          />
        )}
      </Tabs.Navigator>
    </NavigationContainer>
  );
};

目錄屏幕.tsx

import React from "react";
import { FlatList, SafeAreaView, Text, View, StyleSheet } from "react-native";
import { LoadingSpinnerOverlay } from "../components/LoadingSpinnerOverlay";
import { getAllProducts, ProductListData } from "../api/catalog";

class ProductItem extends React.Component<{ item: ProductListData }> {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (
      <View>
        {console.log(`Rendered ${this.props.item.name}-${Math.random()}`)}
        <Text style={{height: 100}}>{this.props.item.name}</Text>
      </View>
    );
  }
}

export class CatalogScreen extends React.PureComponent {
  state = {
    productData: []
  };
  
  componentDidMount() {
    getAllProducts()
    .then(response => {
      this.setState({ productData: response.data });
    })
    .catch(err => {
      console.log(err);
    });
  }

  private renderItem = (props: any) => <ProductItem {...props} />;
  private keyExtractor = (product: any) => `${product.id}`;
  private listItemLayout = (data: any, index: number) => ({
    length: 100,
    offset: 100 * index,
    index
  });

  render() {
    const { productData } = this.state;
    console.log("CATALOG RENDERED");

    return (
      <SafeAreaView style={styles.pageView}>
        {!productData.length && <LoadingSpinnerOverlay text="Loading products..." />}
        <View style={{backgroundColor: "red", height: "50%"}}>
          <FlatList
            data={productData}
            removeClippedSubviews
            keyExtractor={this.keyExtractor}
            renderItem={this.renderItem}
            getItemLayout={this.listItemLayout}
          />
        </View>
      </SafeAreaView>
    );
  }
};

const styles = StyleSheet.create({
  pageView: {
    height: "100%",
    position: "relative",
  }
});

由於我的組件和列表已優化並且我仍然收到錯誤,我開始相信這可能是 React Native 的實際問題 - 但如果有人能看到我做錯了什么或任何解決方法,這個會有很大幫助!

其他發現:我發現如果CatalogScreen組件包含在具有單個屏幕的NativeStackNavigator中,則不再出現警告。 我相信這可能表明這是BottomTabNavigator模塊的問題。

例如,如果我進行以下更改,則不再出現警告:

應用程序.tsx

const AppRoutes = [
  { name: "Home", Component: HomeScreen },
  { name: "Catalog", Component: CatalogPage }, // Changed CatalogScreen to CatalogPage
  { name: "Cart", Component: CartScreen }
];

目錄屏幕.tsx

const Stack = createNativeStackNavigator();

export class CatalogPage extends React.PureComponent {
  render() {
    return (
      <Stack.Navigator>
        <Stack.Screen 
          name="CatalogStack" 
          options={{ headerShown: false }}
          component={CatalogScreen}
        />
      </Stack.Navigator>
    );
  }
}

使用此解決方法,我將直接呈現 Stack Navigation 組件而不是CatalogScreen組件。 這解決了問題,但我不明白為什么這會有所作為。 React Native 在 Stack Navigation 屏幕中與 BottomTabNavigator 屏幕中處理 memory 對象的方式是否不同?

我只是隨機回答一個問題,但充其量只是一個猜測。

您在評論中鏈接到您的問題的視頻使發生的事情更加清楚,那里發生了一些奇怪的事情。

對於像這樣的普通列表,尤其是在移動設備上,您只想渲染和加載當前正在顯示和可見的項目,您不想將所有可能項目的整個列表保留在 memory 中並一直渲染。 這就是為什么你會使用回調之類的東西來呈現項目,以便列表可以在滾動時調用回調並只呈現它需要的項目。

這里說的是我能想到的一些隨機的事情:

  • 確保每個組件,尤其是 item 組件,都是純組件,因此它們不會重新渲染。 確保項目組件不會再次呈現。
  • 嘗試更改您的道具解構,以便您直接接收道具,即({prop1, prop2})而不是帶有...propsprops 如果你像這樣解構道具,它會在每次加載項目時創建一個新的 object。 如果平面列表不斷調用回調並創建大量新對象,這可能是導致您的問題的罪魁禍首之一。 這也可能是產品項目的一個問題,如果它看到它收到了一個新的道具參考,這意味着它是一個不同的 object,那么它必須做一個淺層比較(即使它是一個純組件),數百的道具。 那真的可以減慢它的速度。 結果是它實際上不會重新渲染它,但它仍然會進行數百次淺 object 比較,這是一個緩慢的過程。 所以修復解構,我敢打賭這樣的事情會導致性能問題。
  • 確保不要雙重渲染視圖,不要在實際加載數據之前渲染平面列表,在設置 state 之前只返回后備微調器或加載器,一旦設置了 state 然后返回完整的孩子. 否則,即使沒有數據,您也會雙重渲染整個事物。
  • 如果您使用匿名 function 作為 renderItem 回調,而不是使用 react class 函數,請嘗試看看是否有區別。 我不認為它應該有所作為,但如果沒有其他幫助的話,值得一試,因為我不確定 renderItem 是如何利用this的。

如果一切都失敗了,我建議您在 github 上詢問本地反應,同時嘗試使用替代方案(如果有)。 另一方面,我並沒有真正從視頻中看到任何性能問題,所以也許這也是一個可以安全忽略的錯誤。

這是 React Native 中FlatLists的一個老問題。 React Native 中的 FlatList 並不以性能着稱。 然而,這可以通過檢查 FlatList 中不必要的重新渲染或嵌套組件並確保您的 FlatList 沒有被 ScrollView 嵌套來緩解。 但是您已經說過返回屏幕時不會重新渲染列表,並且它沒有被 ScrollView 嵌套,因此它可能是組件本身。

老實說,我最近遇到了和你一樣的問題,但是我的 FlatList 比你的更復雜。 在真實設備上運行它時,我注意到我沒有遇到問題,對我來說,這只是 Expo Go 上的問題。 我已經完成了我的研究,但我還沒有一針見血,所以我只能提供建議。

建議一

看看這個RecyclerListView package ,看起來很有希望。

建議二

還有這個 package,但是我對這個有點懷疑,我聽說過React Native Optimized Flatlist package 的抱怨。 我很確定我試過了,但它對我沒有任何作用。

另外,請查看有關該主題的這兩個頁面:

如果您使用的是功能組件,將組件包裝在memo中是防止不必要的渲染的好方法,而無需將功能組件轉換為純 class 組件的麻煩。 這篇文章解釋了更多

按照這個例子:

在父組件中:

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

如果您使用的是 class 組件,請通過擴展React. PureComponent 您的React. PureComponent定義中的 PureComponent

class NewsFeeds extends React.PureComponent {
  render() {
    return (
      <FlatList
          data={data}
          initialNumToRender={4}
          refreshing={loading}
          renderItem={_renderitem}
      />
    )
  }
}

暫無
暫無

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

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