我已经实现了一个监听我的数据库文档的代码,当添加一个新文档时,应用程序会将其作为我FlatList中的一个项目呈现。
我的问题是,每当我更新FlatList的数据时,已经渲染的项目就会一次又一次地重新呈现...
由于我的原始代码很复杂,因此我构建了一个Snack: https://snack.expo.io/@victoriomolina/flatlist-re-renders-all-components
我认为问题在于我使用现有数组的浅表副本来更新状态,但是这样做只是为了在添加新项目时重新呈现FlatList。(我不想重新渲染已渲染的项目)。
非常感谢您的帮助。
Pd:在我的原始代码中,FlatList的组件扩展了React.PureComponent
排尿部分
const [posts, setPosts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const { firebase } = props;
let postsArray = [];
// Realtime database listener
const unsuscribe = firebase
.getDatabase()
.collection("posts")
.doc(firebase.getCurrentUser().uid)
.collection("userPosts")
.orderBy("date") // Sorted by upload date
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === "added") {
// Get the new post
const newPost = change.doc.data();
// Add the new post to the posts list
postsArray.unshift(newPost);
}
});
// Reversed order so that the last post is at the top of the list
setPosts([...postsArray]); // Shallow copy of the existing array -> Re-render when new posts added
setIsLoading(false);
});
/* Pd: At the first time, this function will get all the user's posts */
return () => {
// Detach the listening agent
unsuscribe();
};
}, []);
FlatList
<FlatList
data={data}
keyExtractor={keyExtractor}
legacyImplementation={false}
numColumns={1}
renderItem={this.renderItem}
getItemLayout={this.getItemLayout}
initialNumToRender={12}
windowSize={40}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={50}
removeClippedSubviews
ListFooterComponent={this.renderFooter()}
/>
渲染项目方法
renderItem = ({ item, index }) => {
const {
images,
dimensions,
description,
location,
likes,
comments,
date,
} = item;
return (
<View
key={index}
onLayout={({ nativeEvent }) => {
this.itemHeights[index] = nativeEvent.layout.height;
}}
>
<Card { /* Extends React.PureComponent */ }
images={images}
postDimensions={dimensions}
description={description}
location={location}
likes={likes}
comments={comments}
date={date}
/>
</View>
);
};
答案 0 :(得分:1)
数据更新时,组件将重新渲染。 为了防止这种情况,您需要在调用fetchData()
之前执行此行useEffect(() => {
if (data) return;
fetchData();
}, [data]);
* edit:将数据添加到依赖项数组
将会发生的是,useEffect将在组件加载时运行,并将调用fetchData函数,该函数将更新您的状态,因此组件将重新渲染,因此下一个渲染数据将具有任何值,因此if语句将阻止第二次调用提取数据。
我还建议使用空值作为初始数据
const [data, setData] = useState(null);
答案 1 :(得分:1)
我删除了此内容:
onEndReached={fetchData}
,效果很好(see it online)。问题在于,当响应本机结束渲染时,它们会调用onEndReached
。因此您将在每个渲染中再次获得初始数据,这将导致无限渲染问题。 (more)
答案 2 :(得分:1)
对我有用的解决方案:
在渲染项中,我将索引作为项的键传递。我已经读到这会产生奇怪的行为,所以我决定改为传递item.id(这是一个UUID)。
将PureComponent更改为标准组件,然后重新实现componentShouldUpdate生命周期方法。如果您有PureComponent,它将是:
// From the RN documentation
shouldComponentUpdate(nextProps, nextState) {
return nextProps !== this.props && nextState !== this.state;
}
因此,我决定将我的商品更改为常规组件并执行以下操作:
shouldComponentUpdate(nextProps, nextState) {
// My component is just a card with an author row and a progressive image (thumbnail + image) with a Skeleton absolutely positioned
return nextState.avatarIsLoaded && nextState.thumbailIsLoaded; // && nextState.imageIsLoaded is unnecesary by the way I have implemented my component
}
Pd:同样,最好还是这样做,因为如果我添加* && nextState.imageIsLoaded *,我将需要等待很长时间才能加载完整的图像(其大小大于缩略图的大小)。
现在我的项目渲染两次,首先是在显示“骨骼”时,其次是在准备好显示渐进式图像时。