React Native FlatList повторно отображает уже отрисованные элементы при добавлении новых данных - PullRequest
1 голос
/ 05 августа 2020

Я реализовал код, который прослушивает мои документы БД, и когда добавляется новый, приложение отображает его как элемент в моем 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 ]

1 голос
/ 05 августа 2020

Я удалил это:

onEndReached={fetchData}

, и он работал нормально ( см. В Интернете ). Проблема в том, что response-native вызывает onEndReached при завершении рендеринга. поэтому вы снова будете получать исходные данные при каждом рендеринге, и это вызывает проблему бесконечного рендеринга. ( подробнее )

1 голос
/ 05 августа 2020

Когда ваши данные обновляются, компонент перерисовывается. Чтобы предотвратить это, вам необходимо выполнить эту строку перед вызовом fetchData ()

useEffect(() => {
    if (data) return;
    fetchData();
}, [data]);

* edit: добавить данные в массив зависимостей

Что произойдет, так это то, что useEffect запустится, когда компонент загружается и будет вызывать функцию fetchData, которая обновляет ваше состояние, поэтому компонент выполняет повторную визуализацию, поэтому следующие данные визуализации будут иметь любое значение, поэтому оператор if предотвратит второй вызов fetchData.

Я также предлагаю инициализировать данные с нулем

const [data, setData] = useState(null);
0 голосов
/ 06 августа 2020

Решение, которое сработало для меня:

  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 *, мне придется долго ждать, пока не загрузится полное изображение (размер которого больше, чем у эскиза).

Теперь мои элементы визуализируются дважды, во-первых, когда отображается скелет, а во-вторых, когда прогрессивное изображение готово к отображению.

...