添加新数据时,React Native FlatList重新渲染已渲染的项目

时间:2020-08-05 18:26:16

标签: javascript reactjs react-native expo

我已经实现了一个监听我的数据库文档的代码,当添加一个新文档时,应用程序会将其作为我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>
    );
  };

3 个答案:

答案 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)

对我有用的解决方案:

  1. 在渲染项中,我将索引作为项的键传递。我已经读到这会产生奇怪的行为,所以我决定改为传递item.id(这是一个UUID)。

  2. 将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 *,我将需要等待很长时间才能加载完整的图像(其大小大于缩略图的大小)。

现在我的项目渲染两次,首先是在显示“骨骼”时,其次是在准备好显示渐进式图像时。