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

如果你做过 RN 列表,一定经历过这种阶段:

  1. 刚开始:useState 挺顺
  2. 状态多了:开始抽 Redux
  3. 列表卡了:疯狂 memo / useCallback
  4. 还是卡:开始怀疑人生

问题真的在 FlatList 吗?
大多数时候,在状态模型

统一测试场景

为了公平,我们先约定一个非常常见的场景:

  • 一个商品列表(100 条)
  • 每一项可以点赞
  • 点赞状态会影响 UI
  • 不考虑网络,只看本地状态更新

Redux:全局广播型

典型写法

function List() {
  const likedMap = useSelector(state => state.likedMap)

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

一次点赞发生了什么?

我们不讲概念,直接讲链路:

  1. dispatch
  2. reducer 返回新 likedMap
  3. useSelector 命中
  4. List 重新 render
  5. FlatList 重新计算 renderItem
  6. 所有 Item 重新走 props 对比

哪怕你:

  • memo 了 Item
  • useCallback 了 renderItem

List 这一层,永远逃不掉。

Redux 的本质问题

状态变化是“广播式”的,而列表最怕广播。

Redux 很适合:

  • 页级数据
  • 配置
  • 权限
  • 请求缓存

但它对“高频、局部、交互型状态”是天然不友好的。

Jotai:原子级订阅

我们换成 Jotai。


Atom 定义

const likedAtom = atom<Record<string, boolean>>({})

Item 组件直接订阅

function Item({ id }) {
  const [likedMap, setLikedMap] = useAtom(likedAtom)
  const liked = likedMap[id]

  return (
    <Pressable
      onPress={() =>
        setLikedMap(prev => ({
          ...prev,
          [id]: !prev[id]
        }))
      }
    >
      <Text>{liked ? '❤️' : '🤍'}</Text>
    </Pressable>
  )
}

看起来好一点,但问题还在

为什么?

因为:

  • likedMap 还是一个大对象
  • atom 的 value 还是整体变化
  • 所有订阅这个 atom 的组件都会更新

Jotai 没有魔法,它只是更细粒度。

正确的 Jotai 用法

关键在这一步:

const likedAtomFamily = atomFamily((id: string) =>
  atom(false)
)
function Item({ id }) {
  const [liked, setLiked] = useAtom(likedAtomFamily(id))

  return (
    <Pressable onPress={() => setLiked(v => !v)}>
      <Text>{liked ? '❤️' : '🤍'}</Text>
    </Pressable>
  )
}

现在变化的是:

  • 一个 atom
  • 一个 item
  • 一个订阅者

这时 Jotai 的优势才真正出现。

Zustand:选择器驱动型

Zustand 是 RN 圈里这两年非常受欢迎的状态库。

Store 定义

const useStore = create(set => ({
  likedMap: {},
  toggleLike: (id) =>
    set(state => ({
      likedMap: {
        ...state.likedMap,
        [id]: !state.likedMap[id],
      },
    })),
}))

Item 级别订阅

function Item({ id }) {
  const liked = useStore(
    state => state.likedMap[id]
  )
  const toggleLike = useStore(
    state => state.toggleLike
  )

  return (
    <Pressable onPress={() => toggleLike(id)}>
      <Text>{liked ? '❤️' : '🤍'}</Text>
    </Pressable>
  )
}

Zustand 在这里做对了什么?

重点只有一个:

selector 是订阅边界。

  • likedMap 整体变没关系
  • selector 只关心 likedMap[id]
  • 其他 item 不会被通知

这点和 Redux 完全不同。

三者在 RN 列表里的核心差异对比

维度 Redux Jotai Zustand
更新模型 广播 原子订阅 选择器订阅
默认粒度 全局 原子 selector
列表友好度 中(需设计)
心智成本
易踩坑指数

为什么 Zustand 在 RN 圈更“顺手”

不是偶然。

RN 的渲染模型决定了:

  • 谁订阅,谁重渲
  • 渲染成本非常直观
  • 没有浏览器兜底

Zustand 的 selector 模型,天然契合 RN 的这种“显式渲染”。

但 Zustand 也不是银弹

需要注意几个现实问题:

  • store 过大会失控
  • selector 写得不好一样重渲
  • 很多人开始“什么都放 store”

所以记住一句话:

Zustand 是局部状态放大器,不是全局垃圾桶。

一个推荐的组合方案

在真实 RN 项目里,一个非常稳妥的搭配是:

  • Redux:页面数据、接口缓存、权限
  • Zustand / Jotai:列表交互、UI 状态
  • useState:item 内部临时态

这不是“多此一举”,而是按渲染成本分层

从状态库差异,反推 RN 的本质

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

RN 不会帮你隐藏状态设计的错误。

Web 项目里还能靠浏览器苟住,
RN 会把每一次设计失误,直接变成卡顿反馈给你

一句话总结

如果只记一句:

在 RN 列表里,谁能把“谁重渲”控制到最小,谁就赢了。

Redux 赢在秩序,
Jotai 赢在精细,
Zustand 赢在直觉。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐