You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native FlatList列表项重复渲染问题求助

问题:ListItem1组件重复渲染排查与解决

控制台日志显示TrendingMovies组件调用API后,ListItem1(列表项渲染组件)出现重复渲染:索引0-9的项被渲染两次,10-19的项渲染一次,预期每个列表项仅渲染一次,需排查解决。

控制台日志

TrendingMovies renders
 LOG  getMovies called
 LOG  cooode
 LOG  TrendingMovies renders
 LOG  TrendingMovies renders
 LOG  ListItem1 : 0
 LOG  ListItem1 : 1
 LOG  ListItem1 : 2
 LOG  ListItem1 : 3
 LOG  ListItem1 : 4
 LOG  ListItem1 : 5
 LOG  ListItem1 : 6
 LOG  ListItem1 : 7
 LOG  ListItem1 : 8
 LOG  ListItem1 : 9
 LOG  ListItem1 : 0
 LOG  ListItem1 : 1
 LOG  ListItem1 : 2
 LOG  ListItem1 : 3
 LOG  ListItem1 : 4
 LOG  ListItem1 : 5
 LOG  ListItem1 : 6
 LOG  ListItem1 : 7
 LOG  ListItem1 : 8
 LOG  ListItem1 : 9
 LOG  ListItem1 : 10
 LOG  ListItem1 : 11
 LOG  ListItem1 : 12
 LOG  ListItem1 : 13
 LOG  ListItem1 : 14
 LOG  ListItem1 : 15
 LOG  ListItem1 : 16
 LOG  ListItem1 : 17
 LOG  ListItem1 : 18
 LOG  ListItem1 : 19

相关组件代码

TrendingMovies组件

type Props = {};

const TrendingMovies = (props: Props) => {
  const [movies, setMovies] = useState<TrendingMoviesType[]>([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState<string | null>(null);
  const navigation = useNavigation<homeScreenNavigationProp>();

  console.log('TrendingMovies renders');

  useEffect(() => {
    console.log('getMovies called');
    getMovies();
  }, []);

  const getMovies = async () => {
    console.log('cooode');
    setIsLoading(true);
    const result = await trendingMoviesService(currentPage);
    if (result.sucess) {
      setMovies(result.value);
    } else {
      setIsError(result.error.message);
    }
    setIsLoading(false);
  };

  function onItemTap() {
    console.log('onItemTap');
    navigation.navigate('ViewAll');
  }

  const onListItemTap = (id: string) => {
    console.log('onListItemTap');
    navigation.push('MovieDetail', {id: id});
  };

  return (
    <View>
      {isLoading ? (
        <MoviesLoadingPlaceholder />
      ) : isError !== null ? (
        <View></View>
      ) : (
        <View style={styles.listContainer}>
          <View style={styles.headerRow}>
            <AppText
              text={'Trending now'}
              textType="sectionTitle"
              style={styles.sectionTitle}
            />
            <TouchableOpacity onPress={onItemTap}>
              <AppText
                text={'View All'}
                textType="regular"
                style={styles.sectionTitle}
              />
            </TouchableOpacity>
          </View>
          <FlatList
            data={movies}
            renderItem={({item, index}) => (
              <ListItem1
                index={index}
                image={item.poster_path}
                onTap={() => onListItemTap(item.id.toString())}
              />
            )}
            keyExtractor={item => item.id.toString()}
            horizontal={true}
            showsHorizontalScrollIndicator={false}
          />
        </View>
      )}
    </View>
  );
};


export default TrendingMovies;

ListItem1组件

type Props = {
  image: string;
  onTap: () => void;
  index: number;
};

const ListItem1 = ({index, image, onTap}: Props) => {
  console.log('ListItem1 : ' + index);
  return (
    <View>
      <TouchableOpacity onPress={onTap}>
        <AppImage imageURL={baseImageURL + image} />
      </TouchableOpacity>
    </View>
  );
};

export default ListItem1;

排查分析

  1. TrendingMovies多次触发渲染:日志中3次TrendingMovies renders分别对应:
    • 组件初始渲染
    • getMovies中setIsLoading(true)触发的渲染
    • API请求完成后,setMovies和setIsLoading(false)触发的渲染
  2. ListItem1无渲染缓存:ListItem1未使用React.memo,只要父组件渲染且props引用变化,就会触发重渲染。
  3. onTap prop引用频繁变化:FlatList的renderItem中每次都会创建新的匿名函数作为ListItem1的onTap prop,即使item数据未变,也会触发组件重渲染。

解决方案

步骤1:给ListItem1添加React.memo优化

用React.memo包裹组件,让它仅在props浅比较变化时才重新渲染:

type Props = {
  image: string;
  onTap: () => void;
  index: number;
};

const ListItem1 = ({index, image, onTap}: Props) => {
  console.log('ListItem1 : ' + index);
  return (
    <View>
      <TouchableOpacity onPress={onTap}>
        <AppImage imageURL={baseImageURL + image} />
      </TouchableOpacity>
    </View>
  );
};

// 添加React.memo实现缓存
export default React.memo(ListItem1);

步骤2:缓存onListItemTap函数

用useCallback缓存点击函数,避免每次组件渲染创建新的函数引用:

// 在TrendingMovies组件中导入useCallback
import { useCallback } from 'react';

// 替换原onListItemTap定义
const onListItemTap = useCallback((id: string) => {
  console.log('onListItemTap');
  navigation.push('MovieDetail', {id: id});
}, [navigation]); // 仅依赖navigation,避免不必要的函数重建

步骤3:优化renderItem的onTap传递(可选但更彻底)

避免在renderItem中创建匿名函数,改为传递电影ID作为props,在ListItem1内部处理点击逻辑:

修改ListItem1的props与逻辑

type Props = {
  image: string;
  onTap: (id: string) => void;
  index: number;
  movieId: string;
};

const ListItem1 = ({index, image, onTap, movieId}: Props) => {
  console.log('ListItem1 : ' + index);
  return (
    <View>
      <TouchableOpacity onPress={() => onTap(movieId)}>
        <AppImage imageURL={baseImageURL + image} />
      </TouchableOpacity>
    </View>
  );
};

export default React.memo(ListItem1);

修改TrendingMovies中FlatList的renderItem

<FlatList
  data={movies}
  renderItem={({item, index}) => (
    <ListItem1
      index={index}
      image={item.poster_path}
      movieId={item.id.toString()}
      onTap={onListItemTap}
    />
  )}
  keyExtractor={item => item.id.toString()}
  horizontal={true}
  showsHorizontalScrollIndicator={false}
/>

额外优化:缓存onItemTap函数(可选)

同样用useCallback缓存头部的onItemTap函数,避免头部组件不必要的重渲染:

const onItemTap = useCallback(() => {
  console.log('onItemTap');
  navigation.navigate('ViewAll');
}, [navigation]);

效果验证

优化后,TrendingMovies仍会因状态更新渲染3次,但ListItem1仅会在数据首次加载时渲染20次(0-19各一次),不会出现重复渲染的情况。

内容的提问来源于stack exchange,提问作者Shubham Hande

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 11:22:04