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
钩子。
我能够解决这个问题。
缓慢的罪魁祸首是在父组件 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 之类的东西。
感谢大家的帮助。
我正在尝试用 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
钩子。
我能够解决这个问题。
缓慢的罪魁祸首是在父组件 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 之类的东西。
感谢大家的帮助。