为什么 RN 列表性能问题,80% 不在 FlatList 本身?

@[toc]在这里插入图片描述

很多 RN 项目一遇到列表卡顿,第一反应几乎都是一句话:

FlatList 不行,RN 天生慢。

但如果你真的把性能工具打开,把 JS / UI / Native 三条线程的调用链一层层扒开,会发现一个很扎心的事实:

FlatList 只是最后背锅的那一环,真正拖慢你列表的,往往是你自己写的状态和渲染路径。

这篇文章我们不讨论「FlatList 有什么参数可以调」,而是反过来,从一次最普通的交互开始,把 RN 列表性能问题的真实触发链路完整走一遍。

一、先统一认知:FlatList 到底干了什么?

在聊性能之前,先把一个常见误解清掉。

FlatList 本质只做了三件事:

  1. 虚拟化

    • 控制「哪些 item 被渲染」
    • 控制「哪些 item 被回收」
  2. scroll 事件桥接

    • Native 滚动
    • JS 侧接收 offset
  3. 把 item 组件当黑盒渲染

    • FlatList 不关心你 item 里写了什么
    • 它只管你什么时候 renderItem

也就是说:

FlatList 不负责你的 state,不负责你的 Context,不负责你的 Redux。

你列表一滑就掉帧,99% 不是 FlatList 算错了可视区域,而是它被迫“重复干活”了。

二、一次点赞操作,为什么能让整个列表卡住?

我们从一个最日常的需求开始。

场景描述

一个 feed 列表,每一项都有一个「点赞」按钮。

列表
 ├── Item #1 👍
 ├── Item #2 👍
 ├── Item #3 👍
 ...

你点了第 1 条的 👍,结果滑动开始掉帧。

为什么?

三、Demo:一次 state 更新引发的渲染雪崩

问题版本 Demo(可直接跑)

import React, { useState } from 'react';
import { FlatList, Text, TouchableOpacity, View } from 'react-native';

const mockData = Array.from({ length: 100 }, (_, i) => ({
  id: String(i),
  title: `Item ${i}`,
}));

export default function App() {
  const [likedId, setLikedId] = useState<string | null>(null);

  return (
    <FlatList
      data={mockData}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => {
        const liked = item.id === likedId;

        return (
          <View style={{ padding: 16 }}>
            <Text>{item.title}</Text>
            <TouchableOpacity onPress={() => setLikedId(item.id)}>
              <Text>{liked ? '已点赞' : '点赞'}</Text>
            </TouchableOpacity>
          </View>
        );
      }}
    />
  );
}

这个 Demo 做错了什么?

从功能角度看,它是完全正确的。

但从渲染模型来看,问题非常致命。

四、渲染触发链路拆解(重点)

当你点击一次点赞,发生了什么?

1. JS 线程:state 更新

setLikedId(item.id);
  • App 组件 state 发生变化
  • 整个 App 函数重新执行

2. React Reconcile:FlatList 重新 render

  • renderItem 是一个 inline 函数
  • React 无法判断哪些 item “不受影响”

结果是:

FlatList 会重新调用所有可视区域内的 renderItem

哪怕你只点了第一个 item。

3. UI 线程:layout + draw

  • 所有 item 重新生成 element tree
  • Yoga 重新算布局
  • UI 线程绘制新视图

4. Native 线程:scroll 与 JS 竞争时间片

当你正在滑动时:

  • Native 正在处理滚动
  • JS 同时在做 diff + render
  • Bridge 消息频繁来回

这时候你看到的就是:

滑动开始掉帧,甚至有“卡一下”的感觉。

五、FlatList 真的慢吗?不是,是你让它每次都全算了

FlatList 的虚拟化只解决“不可见 item 不渲染”的问题,它解决不了这两件事:

  1. renderItem 被重新调用
  2. item 组件本身被重新 render

也就是说:

FlatList 能保证「不渲染 100 条」,
但保证不了「不重复渲染 10 条」。

六、正确拆法:把状态从列表层“拿走”

改造目标

  • 点赞只影响当前 item
  • 列表不因为一个点赞而整体 rerender

改造后的 Demo

const Item = React.memo(function Item({
  title,
  onLike,
}: {
  title: string;
  onLike: () => void;
}) {
  const [liked, setLiked] = useState(false);

  return (
    <View style={{ padding: 16 }}>
      <Text>{title}</Text>
      <TouchableOpacity
        onPress={() => {
          setLiked(true);
          onLike();
        }}
      >
        <Text>{liked ? '已点赞' : '点赞'}</Text>
      </TouchableOpacity>
    </View>
  );
});

export default function App() {
  return (
    <FlatList
      data={mockData}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <Item
          title={item.title}
          onLike={() => {
            console.log('like', item.id);
          }}
        />
      )}
    />
  );
}

七、这次性能为什么好了?

关键点只有两个:

1. 列表层不再持有 item 状态

  • App 不再有 likedId
  • FlatList 不再因为点赞重跑 renderItem

2. item 变成“局部自洽组件”

  • state 在 Item 内部
  • React.memo 让未变化 item 直接跳过 render

八、JS / UI / Native 线程协作视角再看一次

现在再看这三条线程:

JS 线程

  • 只有一个 Item 的 state 变化
  • 没有大规模 diff

UI 线程

  • 只有一个 item layout 变化
  • 滚动不卡

Native 线程

  • scroll 完全独立
  • bridge 消息极少

你不是“优化了 FlatList”,而是“停止折磨 FlatList”。

九、跨端视角:为什么 Web / Vue 项目更容易“感觉不到问题”

很多人会说:

Web 里我这么写也没事啊。

原因很简单:

  • 浏览器渲染线程够强
  • DOM diff 有大量底层优化
  • scroll 与 JS 解耦得更彻底

但 RN 把这些问题提前暴露出来了

这也是为什么:

RN 的性能问题,本质上是在逼你写“更干净的组件边界”。

而这些边界,回到 Web、Vue、甚至原生,都会变成长期收益。

十、总结

如果你只记住一句话:

RN 列表性能问题,80% 不是 FlatList,而是你把“不该放在列表层的状态”放进去了。

真正有效的优化顺序应该是:

  1. 先拆状态边界
  2. 再看 renderItem 是否稳定
  3. 最后才轮到 FlatList 参数

FlatList 很少是第一个需要被“优化”的对象,它更多时候只是最先暴露问题的地方

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值