用一张“状态扩散图”,定位 RN 列表性能风险



子玥酱 (掘金 / 知乎 / CSDN / 简书 同名)

大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩‍💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。

我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括 前端工程化、小程序、React / RN、Flutter、跨端方案,

在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。

技术方向: 前端 / 跨端 / 小程序 / 移动端工程化 内容平台: 掘金、知乎、CSDN、简书 创作特点: 实战导向、源码拆解、少空谈多落地 **文章状态:**长期稳定更新,大量原创输出

我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在"API 怎么用",而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。

子玥酱 · 前端成长记录官 ✨

👋 如果你正在做前端,或准备长期走前端这条路

📚 关注我,第一时间获取前端行业趋势与实践总结

🎁 可领取 11 类前端进阶学习资源 (工程化 / 框架 / 跨端 / 面试 / 架构)

💡 一起把技术学"明白",也用"到位"

持续写作,持续进阶。

愿我们都能在代码和生活里,走得更稳一点 🌱

文章目录

为什么你需要一张"状态扩散图"

在 RN 项目里,很多性能问题其实都有一个共同特点:

  • 不好复现
  • 不好量化
  • 不好定位

尤其是列表场景,经常出现这种情况:

数据不多

手机性能也不差

偶尔就是掉一帧

这类问题,如果你只靠:

  • "加 memo"
  • "改 FlatList 参数"
  • "试试看能不能顺一点"

基本等于蒙。

真正有效的方式,是在写代码之前就判断:

这个状态更新,会扩散到哪里?

而"状态扩散图",就是把这个问题一次性说明白的工具。

什么是"状态扩散"

先别急着画图,我们先把概念说清楚。

状态扩散不是"状态变多"

很多人以为:

状态多 = 性能差

这是不对的。

真正危险的是:

一个状态更新,会影响多少组件参与 rerender

这就是"扩散半径"。

一个极简定义

你可以把它记成一句话:

状态扩散 = 一次 setState,引发的组件重渲染路径

路径越长、覆盖面越大,性能风险越高。

最危险的"全列表扩散型"

我们先从最常见、也是最危险的一种状态模型开始。

代码结构

tsx 复制代码
function ListPage() {
  const [likedMap, setLikedMap] = useState({})

  return (
    <FlatList
      data={data}
      renderItem={({ item }) => (
        <Item liked={likedMap[item.id]} />
      )}
    />
  )
}

状态扩散图

复制代码
setLikedMap
   ↓
ListPage rerender
   ↓
FlatList rerender
   ↓
renderItem(全部)
   ↓
Item(全部)

风险评估

  • 扩散起点:Item 级交互
  • 扩散终点:整个列表
  • 扩散层级:4 层
  • 扩散数量:N(列表长度)

结论只有一句话:

这是一个"必卡型"结构,只是早晚问题。

Context 驱动的"隐形全量扩散"

这一种,比第一种更容易被忽略。

看起来很"架构化"的写法

tsx 复制代码
<ListContext.Provider value={listState}>
  <FlatList ... />
</ListContext.Provider>
tsx 复制代码
function Item() {
  const { likedMap } = useContext(ListContext)
}

状态扩散图

复制代码
Context value 更新
   ↓
所有 Consumer rerender
   ↓
所有 Item rerender

为什么它特别危险

因为:

  • Context 更新没有 selector
  • 不管你用不用那一项数据
  • 只要 value 引用变了,必 rerender

你会发现一个很残酷的事实:

Context 在列表里,几乎等价于"广播状态"

Redux 的"选择器扩散型"

Redux 经常被误伤,说它"慢",其实问题不在 Redux 本身。

典型写法

tsx 复制代码
const likedMap = useSelector(state => state.likedMap)

状态扩散图

复制代码
dispatch
   ↓
store 更新
   ↓
selector 返回新引用
   ↓
订阅组件 rerender

如果订阅发生在:

  • ListPage
  • renderItem 内
  • Item 内但 selector 粒度过大

那结果就是:

Redux 版本的"全列表扩散"

可控的"局部扩散型"

现在来看一张健康的状态扩散图

Item 自持状态

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

状态扩散图

复制代码
Item setState
   ↓
当前 Item rerender

风险评估

  • 扩散起点:Item
  • 扩散终点:Item
  • 扩散层级:1
  • 扩散数量:1

这是 RN 列表里最理想的状态模型。

Zustand / Jotai 的"精准订阅型"

这类状态管理工具,在 RN 列表里表现好的原因,本质也在这里。

精准订阅示例

tsx 复制代码
const liked = useStore(state => state.liked[item.id])

状态扩散图

复制代码
store 更新
   ↓
命中 selector
   ↓
当前 Item rerender

关键点不在"快",在"窄"

  • 不是 store 更新小
  • 是 rerender 命中范围小

把图连起来:你现在在哪一层?

你可以把 RN 列表里的状态模型,直接分成这几档:

复制代码
Level 1:Item 局部状态
Level 2:Item 精准订阅 store
Level 3:ListPage 局部状态
Level 4:Context 广播状态
Level 5:Redux 大对象订阅

越往下:

  • 状态扩散半径越大
  • 性能风险指数级上升

真实项目中的一个判断方法

以后你看任何一个 RN 列表,只问自己 3 个问题:

  1. 这个状态更新,最先 rerender 的组件是谁?
  2. rerender 会不会回到 FlatList 这一层?
  3. renderItem 会不会因此全量执行?

只要有一个答案是"会",你就已经可以预判:

这个列表,一定会在未来某个需求下卡。

总结

RN 列表性能问题,真正的分水岭不是:

  • 用不用 FlatList
  • 用不用 memo
  • 用不用高端手机

而是:

你有没有在脑子里,提前画过一张"状态扩散图"

一旦你开始用这种方式看代码:

  • 性能问题会提前暴露
  • 架构问题会提前规避
  • 优化不再是救火,而是设计的一部分
相关推荐
fe小陈1 天前
react-nil 逻辑渲染器
react.js
坚持学习前端日记1 天前
桌面端与移动端JS桥技术对比及跨平台实现
开发语言·javascript·harmonyos
ahhdfjfdf1 天前
前端实现带滚动区域的 DOM 长截图导出
前端·javascript·react.js
彭不懂赶紧问1 天前
鸿蒙NEXT开发浅进阶到精通15:从零搭建Navigation路由框架
前端·笔记·harmonyos·鸿蒙
千里马-horse1 天前
Rect Native bridging 源码分析--Bool.h
javascript·c++·react native·react.js·bool
千里马-horse1 天前
Rect Native bridging 源码分析--Dynamic.h
javascript·react native·react.js·dynamic
sophie旭1 天前
Suspense+React.lazy--组件渲染如何暂停 → 等待 → 恢复
前端·javascript·react.js
yyyao1 天前
🔥🔥🔥 React18 源码学习 - React Element
react.js·源码阅读
钟睿1 天前
HarmonyOS弹窗+bindSheet半模态+浮层通用解决方案覆盖全业务场景
android·harmonyos