首页 > 解决方案 > React Native - FlatLists re-renders memoized components (React.memo not working)

问题描述

Main Problem

My FlatList ignores the children components memoizations.

Explanation

I have implemented a custom Component "Grid", which just renders the given data in a FlatList with a Grid layout.

Here is the code:

export default function Grid({
  data = [],
  keyExtractor,
  numColumns = 4,
  initialNumToRender,
  renderItem,
  itemMargin = StyleSheet.hairlineWidth,
  isLoading,
  onEndReachedThreshold = 0.5,
  onEndReached,
  ...props
}) {
  const renderGridItem = (item) => {
    const { index } = item;

    const { width } = Dimensions.get("window");

    const size = Math.floor(
      PixelRatio.roundToNearestPixel(
        (width - itemMargin * (numColumns - 1)) / numColumns
      )
    );

    // We don't want to include a `marginLeft` on the first item of a row
    const marginLeft = index % numColumns === 0 ? 0 : itemMargin;

    // We don't want to include a `marginTop` on the first row of the grid
    const marginTop = index < numColumns ? 0 : itemMargin;

    return renderItem({
      ...item,
      size,
      marginLeft,
      marginTop,
    });
  };

  const renderFooter = () => {
    if (!isLoading) return null;

    return (
      <View style={globalStyles.listFooter}>
        <Loading />
      </View>
    );
  };

  return (
    <FlatList
      {...props}
      data={data}
      keyExtractor={keyExtractor}
      numColumns={numColumns}
      initialNumToRender={initialNumToRender}
      renderItem={renderGridItem}
      ListFooterComponent={renderFooter}
      onEndReachedThreshold={onEndReachedThreshold}
      onEndReached={onEndReached}
      style={globalStyles.listContainer}
      contentContainerStyle={globalStyles.listContainer}
    />
  );
}

Also, I have a top level component, which just renders my memoized component inside my custom Grid component, using a custom renderItem function:

export default function UserPostsGrid({
  listKey,
  data = [],
  numColumns,
  initialNumToRender,
  itemMargin,
  isLoading,
  ListEmptyComponent,
  onEndReached,
  ...props
}) {
  const keyExtractor = useCallback(({ id }) => id, []);

  const renderItem = useCallback(
    ({ item, index, size, marginLeft, marginTop }) => {
      const style = {
        width: size,
        height: size,
        marginLeft,
        marginTop,
      };

      return <UserPostsGridItem item={item} index={index} style={style} />;
    },
    []
  );

  return (
    <Grid
      {...props}
      listKey={listKey}
      data={data}
      keyExtractor={keyExtractor}
      numColumns={numColumns}
      renderItem={renderItem}
      initialNumToRender={initialNumToRender}
      isLoading={isLoading}
      removeClippedSubviews={false}
      ListEmptyComponent={ListEmptyComponent}
      itemMargin={itemMargin}
      onEndReachedThreshold={0.5}
      onEndReached={onEndReached}
    />
  );
}

The thing is, that for some reason, my memoized components which are returned from the renderItem function, are magically re-rendered when the data prop changes.

For example, if I add a new element to my current data list, the FlatList will re-render all of its items.

Here is my memoized component:

function UserPostsGridItem({
  item: {
    index,
    images: [{ uri, thumbnailUri }],
  },
  style,
}) {
  const handleOnPress = () => {
    // TODO - Push card lists stack
    console.log(index);
  };

  console.log("Rerendering grid item!", uri);

  return (
    <TouchableOpacity activeOpacity={0.5} onPress={handleOnPress} style={style}>
      <Image
        uri={uri}
        thumbnailUri={thumbnailUri}
        imageFadeDuration={200}
        thumbnailFadeDuration={100}
        withLoading={false}
        style={globalStyles.flexContainer}
      />
    </TouchableOpacity>
  );
}

function areUserPostsGridItemPropsEqual() {
  console.log("Running areEqual"); // <---- NOTE THIS!
  return true;
}

export default memo(UserPostsGridItem, areUserPostsGridItemPropsEqual);

As you can see, my areEqual method returns true, so I suppose the error is in one of the parent components (Grid or UserPostsGrid).

Any ideas about what am I doing wrong?

Note

After modifying the data prop (state in a parent) from

 data=[{id: "1", ...}]

to

 data=[{id: "2", ...}, {id: "1", ...}]

I get this in the console:

"Rerendering grid item! uri..."
"Rerendering grid item! uri..."

THE ARE EQUAL FUNCTION IS NOT BEING EXECUTED!!!!!!

标签: javascriptreactjsreact-nativememoization

解决方案


好的,问题在于 numColumns > 1。

由于虚拟化,我的组件正在重新安装,而不是重新渲染。


推荐阅读