React Native FlatList 使应用程序在 10 个元素后变得非常慢

React Native FlatList makes app extremely slow after 10 elements

我正在尝试用 react-native 构建一个简单的秒表应用程序。我正在使用 AsyncStorage 将时间记录的数据存储到本地存储中,同时我想显示一个 table 来显示所有记录的时间。核心思想是当一个人按住一个LottieView动画时,它会启动一个计时器,当他们按下时,计时器停止,记录在AsyncStorage中,然后更新table.

10 个元素后,我的 FlatList(在 TimeTable.jsx 内)变得非常慢,我不确定为什么。我相信导致此错误的组件是 TimeTable.jsx,但我不太确定为什么。

src/components/Timer/TimeTable.jsx

import React, {useState, useEffect} from 'react'
import { StyleSheet, FlatList  } from "react-native";
import { Divider, List, ListItem } from '@ui-kitten/components'
import AsyncStorage from '@react-native-async-storage/async-storage';

const getRecordedEventsTable = async (dbKey) => {
  try {
    let currentDataArray = await AsyncStorage.getItem(dbKey);
    return currentDataArray ? JSON.parse(currentDataArray) : [];
  } catch (err) {
    console.log(err);
  }
};

const renderItem = ({ item, index }) => (
  <ListItem
  title={`${item.timeRecorded / 1000} ${index + 1}`}
  description={`${new Date(item.timestamp)} ${index + 1}`}
   />
)

export const TimeTable = ({storageKey, timerOn}) => {
  const [timeArr, setTimeArr] = useState([]);

  useEffect(() => {
    getRecordedEventsTable(storageKey).then((res) => {
        setTimeArr(res)
    })

  }, [timerOn])

  return (
      <FlatList
        style={styles.container}
        data={timeArr}
        ItemSeparatorComponent={Divider}
        renderItem={renderItem}
        keyExtractor={item => item.timestamp.toString()}
      />
  );
};

const styles = StyleSheet.create({
    container: {
      maxHeight: 200,
    },
  });

src/components/Timer/Timer.jsx

import React, {useState, useEffect, useRef} from 'react'
import {
    View,
    StyleSheet,
    Pressable,
} from 'react-native';
import {Layout, Button, Text} from '@ui-kitten/components';
import LottieView from 'lottie-react-native'
import AsyncStorage from '@react-native-async-storage/async-storage';
import {TimeTable} from './TimeTable'

const STORAGE_KEY = 'dataArray'

const styles = StyleSheet.create({
    container: {
        flex: 1, 
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#E8EDFF"
    },
    seconds: {
        fontSize: 40,
        paddingBottom: 50,
    }
})

const getRecordedEventsTable = async () => {
    try {
        let currentDataArray = await AsyncStorage.getItem(STORAGE_KEY)
        return currentDataArray ? JSON.parse(currentDataArray) : []
    } catch (err) {
        console.log(err)
    }
}

const addToRecordedEventsTable = async (item) => {
    try {
        let dataArray = await getRecordedEventsTable()
        dataArray.push(item)
    
        await AsyncStorage.setItem(
            STORAGE_KEY,
            JSON.stringify(dataArray)
            )
    } catch (err) {
        console.log(err)
    }
}

// ...

const Timer = () => {
    const [isTimerOn, setTimerOn] = useState(false)
    const [runningTime, setRunningTime] = useState(0)
    const animation = useRef(null);

    const handleOnPressOut = () => {
        setTimerOn(false)
        addToRecordedEventsTable({
            timestamp: Date.now(),
            timeRecorded: runningTime
        })

        setRunningTime(0)
    }

     useEffect(() => {
        let timer = null

        if(isTimerOn) {
            animation.current.play()

            const startTime = Date.now() - runningTime
            timer = setInterval(() => {
                setRunningTime(Date.now() - startTime)
            })
        } else if(!isTimerOn) {
            animation.current.reset()
            clearInterval(timer)
        }

        return () => clearInterval(timer)
    }, [isTimerOn])

    return (
        <View>
            <Pressable onPressIn={() => setTimerOn(true)} onPressOut={handleOnPressOut}>
                <LottieView ref={animation} style={{width: 300, height: 300}} source={require('../../../assets/record.json')} speed={1.5}/>
            </Pressable>
            <Text style={styles.seconds}>{runningTime/1000}</Text>
            <TimeTable storageKey={STORAGE_KEY} timerOn={isTimerOn} />
            <Button onPress={resetAsyncStorage}>Reset Async</Button>
        </View>
    )
}

export default Timer

任何帮助,不胜感激。谢谢。


编辑: 在控制台中收到以下警告:

VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. Object {
  "contentLength": 1362.5,
  "dt": 25161,
  "prevDt": 368776,

编辑: 在Timer.jsx中,我在渲染函数中有一个文本视图,如下所示: <Text style={styles.seconds}>{runningTime/1000}</Text>,这部分应该显示秒表值并用计时器更新。 随着 FlatList 变大,这部分变得非常滞后。

我怀疑是因为它试图不断地重新渲染,所以子组件 TimeTable.jsx 也在不断地重新渲染?

在我看来你有一个循环:

useEffect(() => {
    getRecordedEventsTable(storageKey).then((res) => {
        setTimeArr(res)
    })

  }, [timeArr, timerOn])

useEffect 将在每次 timeArr 更新时被调用。然后,在你调用异步 getRecordedEventsTable 的内部,每次完成时,它都会调用 setTimeArr,这将设置 timeArr,触发序列再次开始。

要优化 FlatList,您可以使用不同的可用参数。你可以阅读这个 https://reactnative.dev/docs/optimizing-flatlist-configuration.

您也可以考虑对 renderItems 函数使用 useCallback 钩子。

我会推荐阅读这篇文章https://medium.com/swlh/how-to-use-flatlist-with-hooks-in-react-native-and-some-optimization-configs-7bf4d02c59a0

我能够解决这个问题。 缓慢的罪魁祸首是在父组件 Timer.jsx 中,因为每次用户按下按钮时 timerOn 属性都会发生变化,整个子组件正在尝试重新渲染并且正在调用 AsyncStorage每次都叫。这就是 {runningTime/1000} 渲染速度非常慢的原因。因为每次 timerOn 组件更改时,所有子组件都已排队等待重新渲染。

这个问题的解决方案是从 Timer 的父级渲染 Table 组件,而不是在 Timer 组件内部,并在 Timer 中维护一个状态,该状态被传递回父级,然后传递给 Table组件。

这是我的父组件现在的样子:

  const [timerStateChanged, setTimerStateChanged] = useState(false);

  return (
    <View style={styles.container}>
      <Timer setTimerStateChanged={setTimerStateChanged} />
      <View
        style={{
          borderBottomColor: "grey",
          borderBottomWidth: 1,
        }}
      />
      <TimeTable timerOn={timerStateChanged} />
    </View>
  );
};

更好的方法是使用 React context 或 Redux 之类的东西。

感谢大家的帮助。