保存页面时React Native FlatList重复渲染且报key重复错误如何解决?
问题根源
- 重复key警告:更新sessions状态时直接使用了当前闭包捕获的旧sessions值,多次触发数据加载或热更新重新触发初始化逻辑时,会将已存在的列表项重复拼接进数组,导致相同sessionID的条目重复出现,触发key冲突警告。
- 列表项重复渲染:RenderCard组件定义在HomeScreen内部,每次HomeScreen重渲染时都会生成新的RenderCard函数实例,FlatList会判定所有项的渲染组件发生变化,触发全量重渲染。
修复后代码
import React,{useState,useEffect, useCallback, memo} from 'react'; import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, Button } from 'react-native'; import firestore from '@react-native-firebase/firestore'; import { Icon } from "react-native-vector-icons/Ionicons"; // 将RenderCard移到组件外部,用memo包裹避免不必要重渲染 const RenderCard = memo(({item, navigation})=>{ return( <TouchableOpacity onPress={()=>{navigation.navigate('HomeScreen2', {item})}}> <View style={{padding: 10}}> <Text>Title= {item.title}</Text> <Text>Description= {item.description}</Text> </View> </TouchableOpacity> ) }) const HomeScreen = ({navigation}) => { const [sessions,setSessions] = useState([]); const [sessionsPerLoad] = useState(12) const [startAfter,setStartAfter] = useState(null) const [lastPost,setLastPost] = useState(false) const [loading, setLoading] = useState(false) // 新增加载状态,避免并发请求 const getSessions = async (count) => { const sessionArray = []; const querySnap = await firestore() .collection('sessions') .orderBy('createdAt', 'desc') .limit(count) .get() const lastVisible = querySnap.docs[querySnap.docs.length - 1] querySnap.forEach((doc)=> { let sessionData = doc.data() sessionData.sessionID = doc.id sessionArray.push(sessionData) }) return {sessionArray, lastVisible} } const getMoreSessions = async (startDoc, count) => { const sessionArray = []; const querySnap = await firestore() .collection('sessions') .orderBy('createdAt', 'desc') .startAfter(startDoc) .limit(count) .get() const lastVisible = querySnap.docs[querySnap.docs.length - 1] querySnap.forEach((doc)=> { let sessionData = doc.data() sessionData.sessionID = doc.id sessionArray.push(sessionData) }) return {sessionArray, lastVisible} } const getSession = useCallback(async () => { if(loading) return setLoading(true) const sessionsData = await getSessions(sessionsPerLoad) // 改用函数式更新,拿到最新的前序状态 setSessions(prev => [...prev, ...sessionsData.sessionArray]) setStartAfter(sessionsData.lastVisible) // 首次加载数据不足分页阈值,直接标记无更多数据 if(sessionsData.sessionArray.length < sessionsPerLoad) { setLastPost(true) } setLoading(false) }, [sessionsPerLoad, loading]) const getMoreSession = useCallback(async () => { if(lastPost || loading || !startAfter) return setLoading(true) const sessionsData = await getMoreSessions(startAfter, sessionsPerLoad) // 改用函数式更新,避免闭包旧值 setSessions(prev => [...prev, ...sessionsData.sessionArray]) setStartAfter(sessionsData.lastVisible) setLastPost(sessionsData.sessionArray.length === 0) setLoading(false) }, [startAfter, sessionsPerLoad, lastPost, loading]) useEffect(()=>{ getSession() },[getSession]) return( <View> <FlatList data={sessions} renderItem={({item})=><RenderCard item={item} navigation={navigation} />} keyExtractor={(item)=>item.sessionID} onEndReached={getMoreSession} onEndReachedThreshold={0.01} scrollEventThrottle={150} ListFooterComponent={()=> !lastPost && loading && <ActivityIndicator />} /> </View> ); }; const styles = StyleSheet.create({ }); export default HomeScreen
核心修改说明
- 所有
sessions状态更新改用函数式写法,彻底避免闭包捕获旧状态导致的数据重复拼接,解决key冲突警告 RenderCard移到组件外部并使用React.memo包裹,仅当item属性变化时才会重渲染对应列表项,大幅减少不必要的重渲染- 新增加载状态锁,避免并发触发分页请求导致数据重复
- 修正
startAfter初始值为null,避免首次查询异常 - 首次加载时自动判断是否还有更多数据,减少无效请求
内容的提问来源于stack exchange,提问作者Nikhil Jain
相关产品推荐
相关产品推荐

