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

Expo Router中FlatList渲染闪烁、滚动异常问题求助

Expo Router标签页中FlatList闪烁、滚动异常问题排查与解决

问题场景

使用Expo Router开发带标签页的应用,其中一个标签页根路径通过Redirect跳转到/favorites子路径,核心代码如下:

import { Redirect } from "expo-router";

export default function Index() {
    return <Redirect href='/favorites' />
}

收藏页(/favorites)核心代码:

import React, { useState } from 'react'
import ScreenLayout from '../../../components/screen-layout/ScreenLayout'
import { FlatList, View } from 'react-native'
import { Stack } from 'expo-router'
import SwitchCategory from '../../../components/switch-category/SwitchCategory';
import { dataRealEstates, dataVehicles } from '../../../data';
import VehicleCard from '../../../components/vehicle-card/VehicleCard';
import RealEstateCard from '../../../components/real-estate-card/RealEstateCard';

export default function index() {

const [category, setCategory] = useState("VEHICLES");

const vehicles = dataVehicles.filter(vehicle => vehicle.fav);
const realEstates = dataRealEstates.filter(realEstate => realEstate.fav);

const getElements = (category, item = null) => {
    const elements = {
        VEHICLES: { data: vehicles, component: <VehicleCard item={item} /> },
        REAL_ESTATE: { data: realEstates, component: <RealEstateCard item={item} /> },
    };
    return { data: elements[category].data, component: elements[category].component };
}

return (
    <ScreenLayout withHeader>
        <Stack.Screen
            options={{
                headerShown: true,
                headerTitle: "Favorites",
                headerTintColor: "black",
                headerStyle: {
                    backgroundColor: "#F8F8F8",
                },
                headerBackTitle: "Back",
                headerShadowVisible: false
            }}
        />
        <SwitchCategory
            category={category}
            setCategory={setCategory}
            className="mt-2 mb-4"
        />

        <FlatList
            data={getElements(category).data}
            numColumns={2}
            keyExtractor={(item, index) => `${item.id}-${index}`}
            style={{ padding: 5, marginBottom: 20 }}
            renderItem={({ item }) => (
                getElements(category, item).component
            )}
            showsVerticalScrollIndicator={false}
            ItemSeparatorComponent={() => <View style={{ height: 15 }} />}
            columnWrapperStyle={{ gap: 15 }}
        />
    </ScreenLayout>
)
}

遇到的问题

  • FlatList项持续闪烁/意外重渲染:已渲染的卡片频繁闪烁或无理由更新
  • 滚动行为异常:滚动卡顿、位置意外重置,交互时自动回到列表顶部
  • 列表交互不一致:部分列表项点击无响应,返回页面时滚动状态无法保留

已尝试切换为ScrollView、用memo优化FlatList、提取renderItem为useCallback函数,但问题未解决。

解决方案

1. 修复组件动态创建逻辑

原getElements函数每次调用都会生成新的组件实例,导致FlatList判定所有子项更新,触发全量重渲染。直接在renderItem中渲染对应组件,避免函数内动态创建:

// 移除原getElements函数,替换为直接渲染逻辑
const renderItem = ({ item }) => {
  if (category === "VEHICLES") {
    return <VehicleCard item={item} />;
  }
  return <RealEstateCard item={item} />;
};

2. 用useMemo缓存过滤后的数据

每次组件重渲染都会重新执行filter生成新数组,导致FlatList误判数据源变化。用useMemo缓存过滤结果:

const vehicles = useMemo(() => dataVehicles.filter(vehicle => vehicle.fav), []);
const realEstates = useMemo(() => dataRealEstates.filter(realEstate => realEstate.fav), []);

3. 优化keyExtractor,避免使用索引

将索引作为key的一部分会在列表项顺序变化时触发重渲染,直接使用唯一ID:

keyExtractor={(item) => item.id.toString()}

4. 调整标签页路由结构,移除Redirect

Expo Router标签页中使用Redirect可能导致页面组件反复挂载卸载,直接将/favorites设为标签页根路径:

// 修改标签页根文件,直接导入并导出Favorites组件
import Favorites from './favorites';
export default Favorites;

5. 用useCallback缓存renderItem函数

确保renderItem函数引用稳定,避免FlatList因函数变化触发重渲染:

const renderItem = useCallback(({ item }) => {
  return category === "VEHICLES" 
    ? <VehicleCard item={item} /> 
    : <RealEstateCard item={item} />;
}, [category]);

最终优化后的收藏页代码

import React, { useState, useMemo, useCallback } from 'react'
import ScreenLayout from '../../../components/screen-layout/ScreenLayout'
import { FlatList, View } from 'react-native'
import { Stack } from 'expo-router'
import SwitchCategory from '../../../components/switch-category/SwitchCategory';
import { dataRealEstates, dataVehicles } from '../../../data';
import VehicleCard from '../../../components/vehicle-card/VehicleCard';
import RealEstateCard from '../../../components/real-estate-card/RealEstateCard';

export default function index() {
  const [category, setCategory] = useState("VEHICLES");

  // 缓存过滤后的数据
  const vehicles = useMemo(() => dataVehicles.filter(vehicle => vehicle.fav), []);
  const realEstates = useMemo(() => dataRealEstates.filter(realEstate => realEstate.fav), []);

  // 分类数据映射
  const categoryData = useMemo(() => ({
    VEHICLES: vehicles,
    REAL_ESTATE: realEstates
  }), [vehicles, realEstates]);

  // 缓存renderItem函数
  const renderItem = useCallback(({ item }) => {
    return category === "VEHICLES" 
      ? <VehicleCard item={item} /> 
      : <RealEstateCard item={item} />;
  }, [category]);

  return (
    <ScreenLayout withHeader>
      <Stack.Screen
        options={{
          headerShown: true,
          headerTitle: "Favorites",
          headerTintColor: "black",
          headerStyle: {
            backgroundColor: "#F8F8F8",
          },
          headerBackTitle: "Back",
          headerShadowVisible: false
        }}
      />
      <SwitchCategory
        category={category}
        setCategory={setCategory}
        className="mt-2 mb-4"
      />

      <FlatList
        data={categoryData[category]}
        numColumns={2}
        keyExtractor={(item) => item.id.toString()}
        style={{ padding: 5, marginBottom: 20 }}
        renderItem={renderItem}
        showsVerticalScrollIndicator={false}
        ItemSeparatorComponent={() => <View style={{ height: 15 }} />}
        columnWrapperStyle={{ gap: 15 }}
      />
    </ScreenLayout>
  )
}

内容的提问来源于stack exchange,提问作者Anthony Mora Valverde

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 23:54:51