React Native 奇怪的行为来呈现 FlatList

React Native odd behavior to render FlatList

我的目标是一次渲染 20 个项目,而不是渲染 10 个项目,然后在 600 毫秒后重新渲染 20 个项目。

我确保只在获取完成后渲染 FlatList。我依靠 React.useEffectdata 依赖项来重新渲染,以模拟效果 load more when scrolling down。行为是一样的,当向下滚动时,它会重新渲染整个 FlatList 并且每 10 个项目重新渲染一次。

console.log

[Tue Sep 15 2020 08:31:56.797]  LOG      Album() useEffect()
[Tue Sep 15 2020 08:31:57.510]  LOG      Album() renderItem() render 1
[Tue Sep 15 2020 08:31:57.651]  LOG      Album() renderItem() render 2
[Tue Sep 15 2020 08:31:57.758]  LOG      Album() renderItem() render 3
[Tue Sep 15 2020 08:31:57.851]  LOG      Album() renderItem() render 4
[Tue Sep 15 2020 08:31:58.700]  LOG      Album() renderItem() render 5
[Tue Sep 15 2020 08:31:58.175]  LOG      Album() renderItem() render 6
[Tue Sep 15 2020 08:31:58.272]  LOG      Album() renderItem() render 7
[Tue Sep 15 2020 08:31:58.375]  LOG      Album() renderItem() render 8
[Tue Sep 15 2020 08:31:58.525]  LOG      Album() renderItem() render 9
[Tue Sep 15 2020 08:31:58.654]  LOG      Album() renderItem() render 10
[Tue Sep 15 2020 08:32:04.758]  LOG      Album() renderItem() render 1
[Tue Sep 15 2020 08:32:04.940]  LOG      Album() renderItem() render 2
[Tue Sep 15 2020 08:32:05.106]  LOG      Album() renderItem() render 3
[Tue Sep 15 2020 08:32:05.240]  LOG      Album() renderItem() render 4
[Tue Sep 15 2020 08:32:05.420]  LOG      Album() renderItem() render 5
[Tue Sep 15 2020 08:32:05.530]  LOG      Album() renderItem() render 6
[Tue Sep 15 2020 08:32:05.608]  LOG      Album() renderItem() render 7
[Tue Sep 15 2020 08:32:05.737]  LOG      Album() renderItem() render 8
[Tue Sep 15 2020 08:32:05.885]  LOG      Album() renderItem() render 9
[Tue Sep 15 2020 08:32:05.986]  LOG      Album() renderItem() render 10
[Tue Sep 15 2020 08:32:06.310]  LOG      Album() renderItem() render 11
[Tue Sep 15 2020 08:32:06.920]  LOG      Album() renderItem() render 12
[Tue Sep 15 2020 08:32:06.148]  LOG      Album() renderItem() render 13
[Tue Sep 15 2020 08:32:06.201]  LOG      Album() renderItem() render 14
[Tue Sep 15 2020 08:32:06.280]  LOG      Album() renderItem() render 15
[Tue Sep 15 2020 08:32:06.340]  LOG      Album() renderItem() render 16
[Tue Sep 15 2020 08:32:06.414]  LOG      Album() renderItem() render 17
[Tue Sep 15 2020 08:32:06.457]  LOG      Album() renderItem() render 18
[Tue Sep 15 2020 08:32:06.521]  LOG      Album() renderItem() render 19
[Tue Sep 15 2020 08:32:06.589]  LOG      Album() renderItem() render 20

./src/features/Album/index.js

import React from 'react';

import {
  StyleSheet,
  ActivityIndicator,
  SafeAreaView,
  View,
  TouchableOpacity,
  Text,
  FlatList,
  RefreshControl,
  Animated,
} from 'react-native';

import Swipeable from 'react-native-gesture-handler/Swipeable';

const styles = StyleSheet.create({
  item: {
    paddingVertical: 20,
    paddingHorizontal: 10,
  },
  separator: {
    flex: 1,
    height: 1,
    backgroundColor: '#e4e4e4',
    marginLeft: 10,
  },
  leftActions: {
    backgroundColor: '#388e3c',
    justifyContent: 'center',
    flex: 1, // continue to swipe.
  },
  actionText: {
    color: '#fff',
    fontWeight: '600',
    padding: 20,
  },
  rightActions: {
    backgroundColor: '#dd2c00',
    justifyContent: 'center',
    // flex: 1, // continue to swipe.
    alignItems: 'flex-end',
  },
});

function LeftActions(progress, dragX) {
  // console.log('Album() LeftActions()', progress, dragX)
  const scale = dragX.interpolate({
    inputRange: [0, 100],
    outputRange: [0, 1],
    extrapolate: 'clamp',
  });
  return (
    <View style={styles.leftActions}>
      <Animated.Text style={[styles.actionText, {transform: [{scale}]}]}>
        Add to Cart
      </Animated.Text>
    </View>
  );
}

function RightActions({progress, dragX, onPress}) {
  // console.log('Album() LeftActions()', progress, dragX)
  const scale = dragX.interpolate({
    inputRange: [-100, 0],
    outputRange: [1, 0],
    extrapolate: 'clamp',
  });
  return (
    <TouchableOpacity onPress={onPress}>
      <View style={styles.rightActions}>
        <Animated.Text style={[styles.actionText, {transform: [{scale}]}]}>
          Delete
        </Animated.Text>
      </View>
    </TouchableOpacity>
  );
}

function Item({item, onSwipeFromLeft, onRightPress}) {
  // console.log('Album() Item() render')
  return (
    <Swipeable
      renderLeftActions={LeftActions}
      onSwipeableLeftOpen={onSwipeFromLeft}
      renderRightActions={(progress, dragX) => <RightActions progress={progress} dragX={dragX} onPress={onRightPress} />}

    >
      <View style={[styles.item]}>
        <Text>{`${item.id} ${item.title}`}</Text>
      </View>
    </Swipeable>
  );
}

function Separator() {
  return <View style={styles.separator} />;
}

export default function Album() {
  const [isLoading, setLoading] = React.useState(true);
  const [currentPage, setCurrentPage] = React.useState(1);
  // const [itemPerPage, setItemPerPage] = React.useState(20);
  const itemPerPage = 20;
  const [data, setData] = React.useState([]);
  const [error, setError] = React.useState();

  React.useEffect(() => {
    console.log('Album() useEffect()');
    fetch(
      `http://jsonplaceholder.typicode.com/albums?_start=${
        currentPage * itemPerPage - itemPerPage
      }&_limit=${itemPerPage}`,
    )
      .then((response) => response.json())
      .then((json) => setData((initial) => [...initial, ...json]))
      .catch((e) => setError(e))
      .finally(() => setLoading(false));
  }, [currentPage]);

  // console.log('Album()', data);

  // question: 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. {"contentLength": 3179.666748046875, "dt": 627, "prevDt": 658}
  const memo = React.useMemo(() => renderItem, [data]);

  function renderItem({item}) {
    console.log('Album() renderItem() render', item.id);
    return (
      <Item
        item={item}
        onSwipeFromLeft={() => alert('swiped from the left')}
        onRightPress={() => alert('pressed right')}
      />
    );
  }

  function handleRefresh() {
    console.log('Album() handleRefresh()');
    setLoading(true);

    // simulate fetch
    setTimeout(() => {
      setLoading(false);
    }, 1000);
  }

  function handleLoadMore() {
    // fake limitPage
    if (100 / itemPerPage === currentPage) return;
    console.log('Album() handleLoadMore()');

    setCurrentPage((page) => page + 1);
  }

  return (
    <>
      {isLoading ? (
        <ActivityIndicator />
      ) : (
        <>
          {error && <Text>{error}</Text>}
          <Text>Album</Text>
          <SafeAreaView>
            <FlatList
              data={data}
              // Is this parameter bad for performance? Put it into perspective, it renders 1,5x more if this parameter is set to half.
              // initialNumToRender={itemPerPage / 2}

              renderItem={memo}
              keyExtractor={(item) =>
                '_' + Math.random().toString(36).substr(2, 9) + item.id
              }
              refreshControl={
                <RefreshControl
                  refreshing={isLoading}
                  onRefresh={handleRefresh}
                />
              }
              onEndReachedThreshold={0.1}
              onEndReached={handleLoadMore}
              ItemSeparatorComponent={Separator}
            />
          </SafeAreaView>
        </>
      )}
    </>
  );
}

这就是 Flatlist 的工作原理。

Flatlist 建立在 virtualizedList 之上,它只渲染屏幕可见的项目和滚动条,它会自动 remove/unmount 屏幕外的项目,因此提供一种优化的方法来处理巨大的列表。

但在您的情况下,如果它始终为 20,您可以尝试设置 initialNumToRender 并且大多数情况下应该可行。

https://reactnative.dev/docs/flatlist#initialnumtorender

进一步阅读,https://reactnative.dev/docs/optimizing-flatlist-configuration