即使所有组件都已优化,“VirtualizedList:您的列表很大,更新速度很慢”警告

Jor*_*ldt 11 javascript typescript reactjs react-native

在使用 React-Native 组件时,我在尝试避免收到“VirtualizedList:您有一个更新缓慢的大列表”警告时遇到了很多麻烦<FlatList>

我已经做了大量的研究和 Google-Fu 来尝试解决方案,但没有一个解决方案有帮助,甚至这个 GitHub 问题上的解决方案也没有帮助。

要记住的事情:

  • 我在列表项组件中使用shouldComponentUpdate() { return false; },因此它们根本不更新。
  • 渲染 FlatList 的组件已经是PureComponent
  • 我在组件的渲染方法中添加了 console.logs,并可以确认它们不会重新渲染。
  • 我没有使用匿名函数,因此该renderItem组件不会在每次调用时重新初始化。

重要提示:该警告似乎仅在使用 my 切换到单独的选项卡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>
  );
};
Run Code Online (Sandbox Code Playgroud)

目录屏幕.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",
  }
});

Run Code Online (Sandbox Code Playgroud)

由于我的组件和列表已经过优化,但我仍然收到错误,我开始相信这可能是 React Native 的实际问题 - 但如果有人能看到我做错了什么,或者有任何解决方法,这会有很大帮助!

其他发现:CatalogScreen我发现如果组件包含在NativeStackNavigator单个屏幕内 ,则不再出现警告。我相信这可能表明这是模块的问题BottomTabNavigator

例如,如果我进行以下更改,则不会再出现警告:

应用程序.tsx

const AppRoutes = [
  { name: "Home", Component: HomeScreen },
  { name: "Catalog", Component: CatalogPage }, // Changed CatalogScreen to CatalogPage
  { name: "Cart", Component: CartScreen }
];
Run Code Online (Sandbox Code Playgroud)

目录屏幕.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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

通过此解决方法,我将渲染堆栈导航组件而不是CatalogScreen直接渲染该组件。这解决了问题,但我不明白为什么这会产生影响。React Native 在 Stack Navigation 屏幕中处理内存对象的方式与 BottomTabNavigator 屏幕中的内存对象不同吗?

小智 1

我只是随机地给出一个答案,但这充其量只是一个猜测。

您在评论中链接到您的问题的视频使发生的事情更加清楚,那里发生了一些奇怪的事情。

对于像这样的普通列表,尤其是在移动设备上,您只想渲染和加载当前正在显示和可见的项目,您不希望将所有可能项目的整个列表保留在内存中并始终渲染。这就是为什么您要使用回调之类的东西来呈现项目,以便列表可以在滚动时调用回调并仅呈现它需要的项目。

话虽如此,这里有一些我能想到的随机事情:

  • 确保每个组件(包括尤其是项目组件)都是纯组件,这样它们就不会重新渲染。确保项目组件不会再次呈现。
  • 尝试改变你的 props 解构,以便你直接接收 props,即而({prop1, prop2})不是props使用...props. 如果你像这样解构 props,它会在每次加载项目时创建一个新对象。如果平面列表不断调用回调并创建大量新对象,这可能是导致问题的罪魁祸首之一。这也可能是产品项目的一个问题,如果它发现它收到了一个新的 prop 引用,这意味着它是一个不同的对象,那么它必须对数百个进行浅层比较(即使它是纯组件)道具。这确实会减慢速度。结果是它实际上不会重新渲染它,但它仍然会进行数百次浅层对象比较,这是一个缓慢的过程。因此,修复解构,我敢打赌这样的事情会导致性能问题。
  • 确保在数据实际加载之前不要双重渲染视图,不要渲染平面列表,在设置状态之前仅返回后备微调器或加载器,一旦设置状态则返回完整的子级。否则,即使没有数据,您也会双重渲染整个事物。
  • 尝试看看如果使用匿名函数作为 renderItem 回调而不是使用 React 类函数是否会有所不同。我认为这不会产生什么影响,但如果没有其他帮助,那么值得一试,因为我不确定 renderItem 如何利用this.

如果一切都失败了,我建议你在 github 上询问 React Native,同时尝试使用替代方案(如果有)。另一方面,我并没有真正从视频中看到任何性能问题,所以也许这也是一个可以安全忽略的错误。