React Native 列表性能优化完全指南:FlatList vs FlashList v2 vs LegendList 实战对比(2026)

2026年React Native列表组件三大方案实战对比:FlatList的虚拟化、FlashList v2的视图回收、LegendList的双向无限滚动,帮你针对不同场景做出最优选择。

引言:为什么列表性能是 React Native 的核心挑战

做移动开发的人都知道,列表渲染大概是最常见、也最容易掉坑的性能场景。社交信息流、电商商品列表、聊天记录、新闻资讯——你能想到的 App 里几乎都离不开列表。而 React Native 在列表性能这块,说实话,一直让开发者又爱又恨。

到了 2026 年,随着 React Native 新架构(Fabric + JSI)成了强制标准,列表组件的生态发生了不小的变化。除了内置的 FlatList,现在还有 Shopify 出品的 FlashList v2(彻底重写的版本)和 LegendApp 推出的 LegendList(纯 JS 方案的新秀)。三者各有各的长处,选哪个、怎么用、怎么调优,是每个 React Native 开发者迟早要面对的问题。

这篇文章会从底层原理讲到实战代码,帮你真正搞清楚这三个列表组件的差异。

FlatList:内置的虚拟化列表

核心工作原理

FlatList 是 React Native 自带的虚拟化列表组件,底层基于 VirtualizedList。核心思路其实挺简单——只渲染屏幕可见区域附近的元素,其余部分用空白占位符代替。用户滚动时,动态创建新进入视口的组件,销毁离开视口的组件。

听起来挺合理的,对吧?

但问题出在「创建」和「销毁」这两个操作上。每当一个新 Item 进入视口,FlatList 都要从头走一遍完整的组件挂载流程——调 render、创建原生视图、计算布局……如果你的列表项稍微复杂一点(有图片、有嵌套组件、带动画),这个过程就会明显变慢。快速滚动的时候,渲染跟不上滚动速度,就会看到空白区域(Blank Area)一闪而过。我相信很多人都被这个问题折腾过。

FlatList 的性能参数调优

好消息是,FlatList 提供了不少参数让你手动调优渲染行为。下面是最关键的几个:

import { FlatList } from 'react-native';

const OptimizedFlatList = () => (
  <FlatList
    data={data}
    renderItem={renderItem}
    keyExtractor={(item) => item.id}
    // 窗口大小:以可视区域高度为单位,上下各渲染多少倍
    // 默认 21(上下各 10 倍),降低可省内存,但容易出空白
    windowSize={11}
    // 每批渲染的 Item 数量
    // 越大 → 空白越少,但 JS 线程阻塞越久
    maxToRenderPerBatch={5}
    // 首次渲染的 Item 数量
    initialNumToRender={10}
    // 批次渲染间隔(毫秒)
    updateCellsBatchingPeriod={50}
    // 如果所有 Item 高度固定,提供这个可跳过异步布局计算
    getItemLayout={(data, index) => ({
      length: ITEM_HEIGHT,
      offset: ITEM_HEIGHT * index,
      index,
    })}
    // 移除可视区域外的原生视图,释放资源
    removeClippedSubviews={true}
  />
);

这些参数本质上就是在做一个性能 vs 体验的权衡

  • windowSize 越大,预渲染越多,空白越少——但内存占用也越高
  • maxToRenderPerBatch 越大,填充越快,不过每次渲染阻塞 JS 线程的时间也越长
  • getItemLayout 对固定高度列表效果很好,但碰到动态高度就没辙了

FlatList 的固有缺陷

不管你怎么调参数,FlatList 都有几个架构层面绕不过去的问题

  1. 没有组件回收机制——Item 滑出视口就销毁,滑回来就重建。大列表里频繁的挂载/卸载操作会严重拖慢 JS 线程
  2. 空白区域难以根治——快速滚动时渲染跟不上,空白几乎避免不了
  3. 低端 Android 设备表现很差——iOS 还好,原生视图渲染效率高,问题不太明显;但 Android 上的卡顿感就非常明显了(尤其是那些千元以下的机型)
  4. 异步布局测量——旧架构中布局计算需要通过异步 Bridge,延迟不可控

所以结论很明确:小型列表(50 条以内)或者对性能不太敏感的场景,FlatList 完全够用。但要是列表有几百上千条数据,或者 Item 比较复杂,FlatList 就开始力不从心了。

FlashList v2:新架构下的性能王者

从 v1 到 v2 的蜕变

FlashList 是 Shopify 团队开发的高性能列表组件。v1 版本就已经凭借视图回收机制做到了比 FlatList 快 5-10 倍。而 2025 年发布的 v2 是一次彻底的重写,专门为新架构打造,带来了三个很重要的变化:

  1. 不再需要尺寸估算——v1 要你提供 estimatedItemSize,估算不准就会导致布局抖动;v2 利用新架构的同步布局测量能力,自动精确计算尺寸
  2. 纯 JS 实现——v2 彻底移除了原生依赖,iOS、Android、Web 上行为完全一致
  3. 内置瀑布流——不再需要单独的 MasonryFlashList,一个 masonry prop 就搞定

视图回收:FlashList 的核心秘诀

FlatList 和 FlashList 最根本的区别在于怎么处理离开视口的组件。FlatList 的做法是销毁再重建——Item 滚出去就卸载,滚回来就挂载一个全新的实例。

FlashList 则走了视图回收(View Recycling)这条路。它维护一个组件池,Item 滑出屏幕时组件不销毁,而是放回池里。新 Item 要渲染时,直接从池里取一个组件,更新它的 props 就行——比从零创建一个新组件快太多了。

打个比方吧:FlatList 像是每次需要碗就现烧一个,用完就砸碎;FlashList 像是有一柜子碗,用完洗干净放回去,下次直接拿。(我觉得这个比喻还挺形象的。)

FlashList v2 实战示例

基础用法

import { FlashList } from "@shopify/flash-list";
import { View, Text, Image, StyleSheet } from "react-native";
import { useCallback } from "react";

interface FeedItem {
  id: string;
  title: string;
  description: string;
  imageUrl: string;
  author: string;
}

export default function SocialFeed({ data }: { data: FeedItem[] }) {
  // v2 中 memoize renderItem 非常重要!
  const renderItem = useCallback(({ item }: { item: FeedItem }) => (
    <View style={styles.card}>
      <Image source={{ uri: item.imageUrl }} style={styles.image} />
      <View style={styles.content}>
        <Text style={styles.title}>{item.title}</Text>
        <Text style={styles.description} numberOfLines={2}>
          {item.description}
        </Text>
        <Text style={styles.author}>{item.author}</Text>
      </View>
    </View>
  ), []);

  const keyExtractor = useCallback((item: FeedItem) => item.id, []);

  return (
    <FlashList
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
    />
  );
}

const styles = StyleSheet.create({
  card: { backgroundColor: "#fff", borderRadius: 12, marginBottom: 12, overflow: "hidden" },
  image: { width: "100%", height: 200 },
  content: { padding: 16 },
  title: { fontSize: 18, fontWeight: "bold", marginBottom: 4 },
  description: { fontSize: 14, color: "#666", marginBottom: 8 },
  author: { fontSize: 12, color: "#999" },
});

看到没?API 跟 FlatList 几乎一模一样。但底层的视图回收机制让性能直接提升了一个量级。在 Shopify 的实际测试中,JS 线程 CPU 使用率从超过 90% 降到了不到 10%。这个数字确实很夸张,不过人家有生产环境的数据支撑。

瀑布流布局(Masonry)

v2 最让人兴奋的新功能之一就是内置瀑布流。v1 的时候你得导入单独的 MasonryFlashList,v2 只需要加一个 prop:

import { FlashList } from "@shopify/flash-list";

export default function PinterestGrid({ data }) {
  return (
    <FlashList
      data={data}
      numColumns={2}
      masonry  // 就这么简单!
      renderItem={({ item }) => (
        <View style={{ margin: 4, borderRadius: 8, overflow: "hidden" }}>
          <Image
            source={{ uri: item.imageUrl }}
            style={{ width: "100%", height: item.height }}
          />
          <Text style={{ padding: 8 }}>{item.title}</Text>
        </View>
      )}
      keyExtractor={(item) => item.id}
    />
  );
}

要是某些 Item 需要跨列显示(比如精选内容),可以通过 overrideItemLayout 设置 span

<FlashList
  data={data}
  numColumns={3}
  masonry
  overrideItemLayout={(layout, item) => {
    if (item.type === "featured") {
      layout.span = 2; // 精选项占两列
    }
  }}
  renderItem={renderItem}
  keyExtractor={keyExtractor}
/>

维持滚动位置(Chat 场景)

FlashList v2 默认启用了 maintainVisibleContentPosition,列表顶部插入新内容时滚动位置不会跳来跳去。这对聊天界面和实时数据流来说太重要了。

从 v1 迁移到 v2 的注意事项

如果你的项目正在用 FlashList v1,迁移到 v2 时有几个关键点要注意:

  • 移除 estimatedItemSize——v2 不需要也不会读取尺寸估算了
  • 替换 MasonryFlashList——改成 FlashListmasonry prop
  • 确保 props 已 memoize——v2 不再像 v1 那样帮你自动做选择性更新,renderItem 必须用 useCallback 包起来
  • 提供 keyExtractor——v2 强烈建议用有效的 keyExtractor,不然向上滚动可能出现布局闪烁
  • 移除废弃的 props——onBlankAreadisableHorizontalListHeightMeasurementdisableAutoLayout 在 v2 中都不支持了
  • 前提条件——项目必须跑在 React Native 新架构上(0.76+),v2 不支持旧架构

LegendList:纯 JS 新秀的独特价值

为什么还需要另一个列表组件?

你可能会想:有了 FlashList v2 就够了吧?其实不然。LegendList 由 LegendApp 团队开发,它填补了一个很实际的需求:一个同时支持新旧架构、100% 纯 JS、专门为复杂动态场景设计的列表组件

不像 FlashList v2 强制要求新架构,LegendList 在旧架构项目中也能正常跑。对于还在迁移过程中的团队来说,这一点真的很关键。

核心特性

双向无限滚动

LegendList 最亮眼的特性是原生支持双向无限滚动,而且没有闪烁、没有位置跳动。做过聊天应用的人应该都懂这有多难——用户往上滑加载历史消息时,当前阅读位置必须纹丝不动。

import { LegendList } from "@legendapp/list";

export default function ChatScreen({ messages, onLoadMore }) {
  return (
    <LegendList
      data={messages}
      renderItem={({ item }) => <ChatBubble message={item} />}
      keyExtractor={(item) => item.id}
      // 内容对齐到底部——专为聊天场景设计
      alignItemsAtEnd
      // 维持可见内容位置,防止加载历史消息时跳动
      maintainVisibleContentPosition
      // 启用视图回收以提升性能
      recycleItems={true}
      // 到达顶部时加载更多历史消息
      onStartReached={onLoadMore}
      // 预渲染缓冲区(像素)
      drawDistance={300}
    />
  );
}

重点看一下 alignItemsAtEnd 这个 prop——它让列表内容从底部对齐,消息少的时候自动在顶部补上空白。也就是说你不需要用 inverted 来反转列表了,直接避开了反转列表在动画和手势交互方面那些让人头疼的 Bug。如果你做过聊天 UI,应该知道 inverted 有多少坑。

可选的视图回收

FlashList 默认就开启视图回收,而 LegendList 把选择权交给了你:

  • recycleItems={false}(默认)——每次创建新组件,更安全但性能差一些。适合 Item 内部有复杂 state 的场景
  • recycleItems={true}——回收组件实例,性能更好。不过 Item 如果有本地 state 的话,可能会碰到状态被错误复用的问题
// 如果 Item 有本地状态,不开回收更安全
<LegendList
  data={data}
  renderItem={({ item }) => <StatefulCard item={item} />}
  recycleItems={false} // 默认值
/>

// 如果 Item 是纯展示型,开启回收提升性能
<LegendList
  data={data}
  renderItem={({ item }) => <SimpleCard item={item} />}
  recycleItems={true}
/>

调试友好的 API

LegendList 的内部滚动状态 API 提供了丰富的调试数据,包括一个 positionAtIndex 函数,可以拿到任意索引的精确滚动位置。配合 useRef 来用:

import { LegendList, LegendListRef } from "@legendapp/list";
import { useRef } from "react";

function MyList() {
  const listRef = useRef<LegendListRef>(null);

  const scrollToMessage = (index: number) => {
    listRef.current?.scrollToIndex({ index, animated: true });
  };

  return (
    <LegendList
      ref={listRef}
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
    />
  );
}

三者对比:到底该选哪个

性能对比概览

维度 FlatList FlashList v2 LegendList
渲染策略 虚拟化(创建/销毁) 视图回收(默认开启) 可选回收 / 虚拟化
相对 FlatList 性能 基准 快 5-10 倍 快 2-5 倍
是否需要原生依赖 内置 否(v2 纯 JS) 否(纯 JS)
新架构要求 必须 新旧都支持
需要尺寸估算 getItemLayout 可选 不需要 estimatedItemSize 可选
瀑布流支持 原生 masonry prop
双向无限滚动 有限支持 支持 专门优化
聊天列表底部对齐 需 inverted hack maintainVisibleContentPosition alignItemsAtEnd 原生支持
Web 支持 有限 良好 良好
社区生态 最成熟 Shopify 维护,月下载 200 万+ 较新,快速成长中

场景化选型建议

什么时候选 FlatList:

  • 列表数据量小(50 条以内),Item 也不复杂
  • 项目还没升级到新架构,短期内也没有升级计划
  • 想要最少的依赖——FlatList 是内置的,零额外安装
  • 简单的设置页面、选项列表等对性能要求不高的场景

什么时候选 FlashList v2:

  • 项目已经在新架构上跑了
  • 社交信息流、电商商品列表、新闻资讯流这类标准大列表场景
  • 需要瀑布流/网格布局(类似小红书、Pinterest 那种)
  • 极致滚动性能有硬性要求,特别是要兼顾低端 Android 设备
  • 希望 API 和 FlatList 尽量兼容,迁移成本低

什么时候选 LegendList:

  • 聊天应用——双向无限滚动、底部对齐、不用 inverted,这三个组合在一起真的很香
  • 项目还在旧架构但想要比 FlatList 更好的列表性能
  • 列表 Item 有复杂本地状态,需要精确控制是否回收
  • 实时数据频繁更新的场景(直播列表、股票行情之类的)
  • 追求零原生依赖,想让集成和调试都更简单

通用列表性能优化最佳实践

不管你最终选了哪个列表组件,下面这些优化技巧都是通用的。

1. 永远 memoize renderItem

这条是最基本的,也是最容易被忽略的。内联箭头函数会在父组件每次重渲染时被重新创建,结果就是列表里所有 Item 全部跟着重渲染一遍:

// ❌ 错误:每次渲染都重新创建函数
<FlashList
  data={data}
  renderItem={({ item }) => <Card item={item} />}
/>

// ✅ 正确:用 useCallback 缓存
const renderItem = useCallback(
  ({ item }) => <Card item={item} />,
  []
);
<FlashList data={data} renderItem={renderItem} />

2. 列表项组件尽量简洁

列表的性能瓶颈往往不在列表组件自身,而在你写的列表项组件。几个实用的优化方向:

  • 减少嵌套——每多一层 View 就多一次布局计算
  • 列表中用缩略图——别在列表里加载原图,进详情页再说
  • 别在 renderItem 里做重计算——移到数据层或者用 useMemo
  • React.memo 包裹列表项(没用 React Compiler 的话)
// ✅ 推荐:简洁的列表项 + React.memo
const FeedCard = React.memo(({ item }: { item: FeedItem }) => (
  <View style={styles.card}>
    <Image
      source={{ uri: item.thumbnailUrl }} // 用缩略图!
      style={styles.thumbnail}
    />
    <Text style={styles.title}>{item.title}</Text>
  </View>
));

// 2026 年用了 React Compiler 的话,可以不手动 memo
// 编译器会自动搞定组件 memoization

3. 善用 getItemType 区分类型

如果列表里有多种类型的 Item(广告卡片、普通卡片、分隔线什么的),一定要告诉列表组件它们的类型。这样回收池就能按类型分组,不会把广告卡片回收后拿去渲染普通内容:

<FlashList
  data={feedData}
  renderItem={({ item }) => {
    switch (item.type) {
      case "ad": return <AdCard item={item} />;
      case "post": return <PostCard item={item} />;
      case "story": return <StoryCard item={item} />;
    }
  }}
  getItemType={(item) => item.type} // 告诉 FlashList 有多种类型
  keyExtractor={(item) => item.id}
/>

4. 别在 Item 里用 key prop

这个坑说真的很多人都不知道。如果你在列表项组件内部加了 key prop,React 会强制把组件销毁后重建,视图回收机制就完全失效了。FlashList 和 LegendList 的性能优势白白浪费:

// ❌ 严禁:在 Item 内部用 key
const renderItem = ({ item }) => (
  <View key={item.id}>  {/* 这会破坏回收! */}
    <Text>{item.title}</Text>
  </View>
);

// ✅ 正确:key 交给列表组件通过 keyExtractor 管理
const renderItem = ({ item }) => (
  <View>
    <Text>{item.title}</Text>
  </View>
);

5. 图片优化是重中之重

图片加载基本上是列表卡顿的头号元凶。建议这么做:

  • expo-imagereact-native-fast-image 代替默认 Image——缓存策略和加载性能都好很多
  • 渐进式加载——先显示模糊占位图(BlurHash),图片加载完再淡入
  • 控制请求尺寸——让后端返回跟显示尺寸匹配的图片,别动不动就拉原图

新架构如何从根本上改变列表性能

React Native 新架构(Fabric + JSI + TurboModules)对列表性能的提升是质变级别的。理解这个背景对选型思路很有帮助。

同步布局测量

旧架构里,测量一个 View 的尺寸得通过异步 Bridge——发出请求、等结果返回,中间的延迟不可预测。新架构通过 JSI 做到了同步布局测量:组件渲染后在 useLayoutEffect 里就能立刻拿到精确尺寸。

这也是 FlashList v2 能够甩掉尺寸估算的根本原因——它在 useLayoutEffect 里测量实际尺寸,计算精确位置,赶在浏览器绘制之前完成修正。用户看到的始终是布局正确的列表。

JSI 消除序列化开销

旧架构中 JS 线程和 UI 线程通信要做 JSON 序列化/反序列化,对于频繁滚动的列表来说,这是一笔持续不断的性能税。新架构的 JSI 让 JS 直接持有 C++ 对象引用,调方法不用序列化——用之前流行的比喻来说,就是从「写信」变成了「打电话」。

React Compiler 的助力

2026 年做列表优化还多了一个新武器:React Compiler。它在编译阶段就自动分析组件的数据流,注入最优的 memoization 逻辑。实际意义是:

  • 不用再手动给每个列表项组件包 React.memo
  • useMemouseCallback 也不是必须手写的了——编译器会代劳
  • 但理解原理依然重要——编译器能优化常见模式,碰到复杂场景还是得手动调

实战案例:从 FlatList 迁移到 FlashList v2

说了这么多理论,来看一个完整的迁移案例。假设你有个电商商品列表,目前用 FlatList,滚动卡顿比较明显:

迁移前(FlatList)

import { FlatList, View, Text, Image, StyleSheet } from "react-native";

export default function ProductList({ products }) {
  return (
    <FlatList
      data={products}
      numColumns={2}
      keyExtractor={(item) => item.id}
      getItemLayout={(data, index) => ({
        length: 280,
        offset: 280 * Math.floor(index / 2),
        index,
      })}
      windowSize={11}
      maxToRenderPerBatch={6}
      removeClippedSubviews={true}
      renderItem={({ item }) => (
        <View style={styles.productCard}>
          <Image source={{ uri: item.image }} style={styles.productImage} />
          <Text style={styles.productName}>{item.name}</Text>
          <Text style={styles.productPrice}>¥{item.price}</Text>
        </View>
      )}
    />
  );
}

迁移后(FlashList v2)

import { FlashList } from "@shopify/flash-list";
import { View, Text, Image, StyleSheet } from "react-native";
import { useCallback } from "react";

export default function ProductList({ products }) {
  const renderItem = useCallback(({ item }) => (
    <View style={styles.productCard}>
      <Image source={{ uri: item.image }} style={styles.productImage} />
      <Text style={styles.productName}>{item.name}</Text>
      <Text style={styles.productPrice}>¥{item.price}</Text>
    </View>
  ), []);

  const keyExtractor = useCallback((item) => item.id, []);

  return (
    <FlashList
      data={products}
      numColumns={2}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      // 不再需要 getItemLayout、windowSize、maxToRenderPerBatch!
      // FlashList v2 自动处理这些
    />
  );
}

迁移步骤总结:

  1. 安装——npm install @shopify/flash-list@^2.0.0
  2. 换导入——从 react-nativeFlatList 改成 @shopify/flash-listFlashList
  3. 删掉手动优化参数——getItemLayoutwindowSizemaxToRenderPerBatchremoveClippedSubviews 统统不要了
  4. memoize 回调——renderItemkeyExtractoruseCallback 包一下
  5. 实机测试——找台低端 Android 设备滚一滚,感受一下性能差异

整个过程通常几分钟就能搞定,效果却很明显。

常见问题

FlashList v2 能在旧架构项目中使用吗?

不行。FlashList v2 是专门给新架构做的,必须 React Native 0.76+ 且启用了新架构。如果你还在旧架构上,有两条路:继续用 FlashList v1.x,或者试试同时支持新旧架构的 LegendList。

FlatList 还值得用吗?要不要全换成 FlashList?

小型简单列表的话,FlatList 完全没问题。二三十条数据、Item 也不复杂,FlatList 性能足够,没必要多引入一个依赖。但数据量大或者 Item 复杂的场景,还是建议迁移到 FlashList v2 或 LegendList。

LegendList 和 FlashList v2 哪个性能更好?

单论原始滚动性能,FlashList v2 通常更优——默认视图回收加上 Shopify 大规模生产验证,底子很扎实。但 LegendList 在动态高度双向滚动方面有独到的优势,空白闪烁也相对更少。老实说,对大多数项目而言,两者的性能差距不会是决定性因素,更重要的是看功能需求匹配度。

用了 React Compiler 还需要手动 memoize 列表项吗?

如果项目启用了 React Compiler(React 19+),编译器会在编译时自动注入 memoization,大多数情况下不用再手动写 React.memouseMemouseCallback。不过搞清楚原理还是有必要的——编译器处理的是常见模式,复杂场景(ESLint 规则会提醒你的)还是要自己来。

从 FlatList 迁移到 FlashList v2 改动大吗?

不大。FlashList 的 API 和 FlatList 高度兼容,基本就三步:换导入、用 useCallbackrenderItem、删掉那些不再需要的手动优化参数。多数情况下几分钟就搞定了。

关于作者 Editorial Team

Our team of expert writers and editors.