RN 列表里的局部状态和全局状态边界

@[toc]

如果你做过稍微复杂一点的 RN 列表,一定遇到过这种情况:

  • 点赞卡
  • 展开卡
  • 勾选卡
  • 编辑卡

一开始觉得都是"小状态",就随手往上提:

  • 提到列表组件
  • 再提到页面
  • 最后进 Redux / Context

然后某一天你发现:

明明只点了一个 item,整个列表却跟着一起抖了一下。

这篇我们就专门拆:
RN 列表里,状态到底该放哪?

一、先说一个容易被忽略的前提

在 RN 里:

render 本身就是成本。

不像 Web:

  • DOM diff 快
  • 浏览器能兜底
  • 掉帧不一定立刻可感知

RN 是:

  • JS 线程 + UI 线程
  • render 和交互强耦合
  • 一点浪费就能直接掉帧

所以"状态放哪"这件事,本质是渲染影响面的问题

二、一个非常典型的错误结构

场景

一个点赞列表,每个 item 都可以点 👍。

问题代码

tsx 复制代码
function Page() {
  const [likedIds, setLikedIds] = useState<string[]>([]);

  const toggleLike = (id: string) => {
    setLikedIds((prev) =>
      prev.includes(id)
        ? prev.filter((i) => i !== id)
        : [...prev, id]
    );
  };

  return (
    <FlatList
      data={data}
      renderItem={({ item }) => (
        <Item
          item={item}
          liked={likedIds.includes(item.id)}
          onLike={toggleLike}
        />
      )}
    />
  );
}

问题不在逻辑,而在位置。

三、一次点赞,到底触发了什么?

我们走一遍真实链路。

1. 点赞触发 Page 的 setState

ts 复制代码
setLikedIds(...)

2. Page 组件 re-render

  • FlatList 重新执行
  • renderItem 重新创建

3. 所有 Item 都被重新计算 props

即使:

  • 只有一个 id 变化
  • 99 个 item 的 liked 值没变

4. JS 线程压力瞬间放大

你会看到:

  • 滑动卡顿
  • FPS 掉到 40 以下
  • 但 FlatList 本身"什么都没做错"

四、这里的根本问题是什么?

一句话:

你把「局部状态」升级成了「全局状态」。

点赞状态本质是:

  • 只影响当前 item
  • 不影响列表结构
  • 不影响其他 item

但你却让它:

  • 由 Page 管理
  • 参与列表整体 render

五、什么是 RN 列表里的"局部状态"?

判断标准非常简单。

如果满足下面 3 条,几乎一定是局部状态:

  1. 只影响单个 item 的 UI
  2. 不影响列表排序 / 数量
  3. 不需要被其他页面感知

比如:

  • 点赞是否高亮
  • 展开/收起
  • 输入框内容
  • 临时动画状态

六、正确写法:状态下沉到 Item

改造后的代码

tsx 复制代码
const Item = React.memo(function Item({ item }) {
  const [liked, setLiked] = useState(false);

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

发生了什么变化?

  • 点赞只触发当前 Item render
  • FlatList 完全不动
  • JS 线程压力极小

七、那全局状态到底该放什么?

很多人会走到另一个极端:

那是不是状态都应该放 item 里?

当然不是。

在 RN 列表里,全局状态通常只有三类

1. 影响列表结构的
  • 排序
  • 过滤条件
  • 分组规则
2. 影响列表数据源的
  • 请求结果
  • 分页游标
  • 服务端状态
3. 跨页面共享的
  • 用户身份
  • 权限
  • 全局配置

八、一个边界模糊但很常见的例子

勾选列表(批量操作)

很多人第一反应:

勾选状态是不是局部?

答案是:一半一半

  • 单个 item 的勾选是局部
  • 勾选集合(选了几个)是全局

合理拆法

tsx 复制代码
function Page() {
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());

  const toggle = (id: string) => {
    setSelectedIds((prev) => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  };

  return (
    <FlatList
      data={data}
      renderItem={({ item }) => (
        <Item
          item={item}
          selected={selectedIds.has(item.id)}
          onToggle={toggle}
        />
      )}
    />
  );
}

这里的关键是:

  • 集合是全局
  • UI 变化是局部
  • 不要在 Page 里做 UI 逻辑

九、为什么 RN 对"状态位置"这么敏感?

核心原因只有一个:

RN 没有浏览器级别的性能兜底。

  • 每次 render 都是真金白银
  • JS 线程一忙,交互立刻受影响
  • 列表又是 render 密集区

所以 RN 项目天然会逼你思考:

  • 状态是不是放太高了?
  • 这个状态真的需要全局吗?

十、从 RN 反看 Web,其实也是好习惯

如果你用 RN 的标准去看 Web 项目,会发现:

  • 很多 Vue / React Web 项目状态提得太高
  • Context / provide 被滥用
  • 列表渲染边界模糊

只是 Web 帮你兜住了而已。

十一、一句话总结

如果只记住一句:

RN 列表性能的关键,不是用什么组件,而是状态影响了多少 render

局部状态就待在 item 里,

全局状态只管结构和数据。

相关推荐
程琬清君2 小时前
前端动态标尺
开发语言·前端·javascript
小此方2 小时前
Re: ゼロから学ぶ C++ 入門(九)类和对象·最终篇上:缓冲区同步与流绑定、取地址运算符重载、const成员函数、初始化列表
开发语言·c++·底层
技术净胜2 小时前
Python常用框架介绍
开发语言·python·sqlite
0思必得02 小时前
[Web自动化] Web安全基础
运维·前端·javascript·python·自动化·html·web自动化
曹轲恒2 小时前
jvm 局部变量表slot复用问题
java·开发语言·jvm
HMS Core2 小时前
HarmonyOS SDK携手Remy让普通手机即可完成专业级3D空间重建
3d·智能手机·harmonyos
天天向上vir2 小时前
防抖与节流
前端·typescript·vue
222you2 小时前
SpringMVC的单文件上传
java·开发语言
宇珩前端踩坑日记2 小时前
怎么让 Vue DevTools 用 Trae 打开源码
前端·trae