用 RN 的渲染模型,反推 Vue 列表的正确拆分方式

@[toc]

很多人会觉得:

RN 卡,是 RN 的问题

Web 顺,是浏览器牛

但当你同时踩过 RN 和 Web 两边的坑之后,反而会意识到一件事:

RN 不是"更差",而是更早、更赤裸地暴露了架构问题。

而 Vue 列表里,很多"看起来没出事"的写法,其实只是被浏览器兜住了而已

一、为什么 RN 的问题,反而更有"教学价值"

先说一个结论:

RN 的渲染模型,比 Vue 更"诚实"。

在 RN 里:

  • 每一次 state 更新
  • 每一次 render
  • 每一次 prop 变化

都会非常直接地体现在:

  • 掉帧
  • 滑动卡顿
  • 动画不跟手

它不会帮你兜底,也不会"差不多就算了"。

而 Web:

  • 浏览器 diff 更快
  • DOM 操作被高度优化
  • 滚动是原生的

于是很多问题被延迟暴露,甚至永远不暴露。

二、RN 列表里我们已经学到的三条铁律

在反推 Vue 之前,先回顾一下 RN 列表中已经被反复验证过的结论:

1. 列表性能问题,本质是"渲染扩散"

不是 FlatList 慢,而是:

  • 一个状态变化
  • 引发了不该更新的 item

2. 列表最怕"广播式状态"

  • Context
  • Redux
  • 父组件 state

只要被列表整体订阅,都会放大问题。

3. Item 的"交互态"必须局部化

点赞、选中、展开,这些状态:

  • 生命周期短
  • 影响范围小
  • 不应该穿透列表

三、把这三条,直接套回 Vue 列表

现在我们来看一个非常常见、甚至被当成"规范"的 Vue 写法。

四、一个看似合理,但暗藏风险的 Vue 列表示例

常见写法

vue 复制代码
<template>
  <div v-for="item in list" :key="item.id">
    <Item
      :item="item"
      :selected="selectedId === item.id"
      @select="handleSelect"
    />
  </div>
</template>

<script setup>
const selectedId = ref(null)

function handleSelect(id) {
  selectedId.value = id
}
</script>

在 Web 里:

  • 点一个 item
  • UI 正常
  • 性能看起来没问题

但如果你用 RN 的视角 看这段代码,会立刻警觉。

五、用 RN 的视角"拆渲染链路"

当你点击某一项时:

  1. selectedId 更新
  2. 父组件重新 render
  3. v-for 重新执行
  4. 所有 Item 的 props 重新计算
  5. 所有 Item 都进入一次 diff

只是因为浏览器扛得住,你才没感觉到。

逻辑扩散是真实存在的

六、RN 会怎么写这件事?

在 RN 里,这种写法几乎一定会被改。

RN 的第一反应是:

这个 selected 状态,真的需要放在列表外吗?

于是更合理的写法是:

jsx 复制代码
const Item = React.memo(({ item }) => {
  const [selected, setSelected] = useState(false)

  return (
    <Pressable onPress={() => setSelected(v => !v)}>
      <Text>{selected ? '选中' : '未选中'}</Text>
    </Pressable>
  )
})

点击:

  • 只更新当前 item
  • 列表不动
  • 渲染范围极小

七、反推 Vue:列表 item 的交互态,应该内聚

改造后的 Vue 写法

vue 复制代码
<Item :item="item" />
vue 复制代码
<script setup>
const props = defineProps<{ item: Item }>()

const selected = ref(false)

function toggle() {
  selected.value = !selected.value
}
</script>

这时候:

  • 父组件不再感知 selected
  • 列表不再参与交互渲染
  • diff 范围被限制在 item 内

八、Vue 里"看起来没问题"的 Context / Store,用 RN 一看全是坑

再看一个更隐蔽的例子。

Vue 中央 store 驱动列表 UI

js 复制代码
const selectedId = computed(() => store.state.selectedId)
vue 复制代码
<Item
  v-for="item in list"
  :key="item.id"
  :active="item.id === selectedId"
/>

这在 RN 世界里等价于:

把列表交互态放进 Redux

我们已经知道结果了。

九、RN 教会我们的:UI 状态 ≠ 业务状态

这是跨端开发者最重要的一条分水岭。

业务状态适合:

  • Vuex / Pinia
  • Redux / Zustand

UI 交互态适合:

  • Item 内部 state
  • 组件私有响应式变量

如果你在 Vue 里区分不清这两类状态,很可能只是:

浏览器替你兜住了后果。

十、Vue 列表的"正确拆分模型",用 RN 语言总结

我们可以直接把 RN 的最佳实践翻译成 Vue 规则:

1. 列表父组件

只做三件事:

  • 数据来源
  • key 管理
  • 布局结构

2. Item 组件

必须满足:

  • 自己管理交互态
  • 尽量少依赖外部响应式数据
  • props 只接收"稳定数据"

3. 状态上浮的唯一理由

只有一种情况值得上浮:

多个 item 之间,真的存在强一致性需求。

比如:

  • 只能选中一个
  • 需要统一取消
  • 跨 item 联动

即便如此,也要尽量:

  • 用 id 而不是对象
  • 用最小订阅范围

十一、一个"用 RN 反推 Vue"的完整对照 Demo

错误版(扩散模型)

vue 复制代码
<script setup>
const activeId = ref(null)
</script>

<Item
  v-for="item in list"
  :active="item.id === activeId"
/>

正确版(局部模型)

vue 复制代码
<Item v-for="item in list" :key="item.id" />

Item 内部:

vue 复制代码
const active = ref(false)

十二、你会发现一个非常反直觉的结果

当你开始用 RN 的模型写 Vue:

  • Vue 列表更好拆
  • 组件边界更清晰
  • 状态职责更干净

这不是因为 Vue 变强了,而是因为:

你不再依赖浏览器的"性能宽容度"了。

相关推荐
UXbot3 小时前
AI原型设计工具如何支持团队协作与快速迭代
前端·交互·个人开发·ai编程·原型模式
ZC跨境爬虫4 小时前
跟着MDN学HTML_day_48:(Node接口)
前端·javascript·ui·html·音视频
PieroPc5 小时前
CAMWATCH — 局域网摄像头监控系统 Fastapi + html
前端·python·html·fastapi·监控
巴巴博一6 小时前
2026 最新:Trae / Cursor 一键接入 taste-skill 完整教程(让 AI 前端告别“AI 味”)
前端·ai·ai编程
kyriewen6 小时前
半夜三点线上崩了,AI替我背了锅——用AI排错,五分钟定位三年老bug
前端·javascript·ai编程
kyriewen7 小时前
我让 AI 当了 24 小时全年无休的“毒舌考官”
前端·ci/cd·ai编程
hexu_blog7 小时前
vue+java实现图片批量压缩
java·前端·vue.js
IT_陈寒7 小时前
为什么你应该学习JavaScript?
前端·人工智能·后端
lifejump7 小时前
Empire(帝国)CMS 7.5 XSS注入
前端·安全·xss