使用indexOf查找对象结合Pinia持久化引发的问题

使用 indexOf 查找对象结合 Pinia 持久化引发的问题

1. 问题

最近写代码的时候我遇到一个问题,我在开启了数据持久化的 Pinia 仓库里面写了一个类似"点击切换选中状态"的逻辑:如果数组里没有就 push 进去,有了就 splice 掉。代码如下:

tsx 复制代码
// Pinia 仓库逻辑
const setAddonItems = (addon: IStep3) => {
  const index = addons.value.indexOf(addon) // 查找当前点击的对象是否存在
  if (index === -1) {
    addons.value.push(addon) // 不存在则添加
  } else {
    addons.value.splice(index, 1) // 存在则移除
  }
}

这代码单看好像没有什么问题,但是我却遇到一个很奇怪的现象,明明逻辑没问题,为什么刷新页面后 Pinia 就"失忆"了?当我每次在这个代码运行的页面进行刷新的时候,刷新前存进仓库的数据就好像没了一样,setAddonItems 函数在调用的时候,却对 addons 里面存储的数据视而不见,好像里面就是空的一样。我盯着那行 indexOf(addon) 感觉非常疑惑:数据明明就在那儿,为什么 JS 却视而不见?

以上图片就是我当时 localStorage 里面 addons 数据的情况,正确的应该是不论页面刷新几次,内容相同的数据都不会重复出现,但是现在却因为页面刷新而出现了重复。

这是为什么呢?

2. 原因

原因其实很简单,就是 indexOf 对于数据里面对象数据是否存在的判断规则,以及 Pinia 持久化把数据存入 localStorage 时 localStorage 的反序列化。

indexOf() 判断对象数据存在的规则

我们都知道,在 JS 中,基本类型是按 值 比较相等的,而对象是按 内存地址(引用) 比较相等的。

js 复制代码
const a = { id: 1 };
const b = { id: 1 };

console.log(a === b); // 输出 false

虽然 a 和 b 的内容一模一样,但它们在内存中占据了两个不同的位置。indexOf 内部使用的是全等运算符(===),因此它判定这两个对象"不相等"。

持久化造成的"孪生兄弟"错觉(localStorage 反序列化 )

当我们开启了 persist: true(Pinia 持久化),流程如下:

  1. 存储时: 插件将内存中的 JS 对象通过 JSON.stringify() 转化成字符串存入 localStorage。
  2. 刷新页面: 插件从 localStorage 读取字符串,通过 JSON.parse() 还原成对象。

关键点就在这里: JSON.parse() 产生的是一个全新的对象实例,这代表这个新的对象实例的引用地址已经和之前的地址不再一样了。

  • 页面中的数据(items.STEP3): 是我们代码里定义的原始对象。
  • 仓库中的数据(addons): 是从 localStorage 反序列化回来的新对象。

即便它们长得一模一样,但在 JS 眼里,它们是住在不同地址的"孪生兄弟"。因此,indexOf(addon) 永远找不到那个本该存在的对象,导致判断失效。

3. 解决办法

用 id 判断,而不是对象(简单,改动少)

ts 复制代码
const setAddonItems = (addon: IStep3) => {
  const index = addons.value.findIndex(
    item => item.id === addon.id
  )

  if (index === -1) {
    addons.value.push(addon)
  } else {
    addons.value.splice(index, 1)
  }
}

只存 id 数组(更推荐)

在大型项目中,为了性能和数据一致性,通常不建议在状态仓库中存储整个对象列表。

推荐做法:

  1. 仓库只存储选中的 ID(例如 string[] 或 number[])。
  2. 显示逻辑通过 ID 到原始数据源中匹配。
ts 复制代码
const selectedIds = ref<string[]>([])

const toggleAddon = (id: string) => {
  const index = selectedIds.value.indexOf(id) // ID 是基本类型,indexOf 此时是可靠的
  if (index === -1) {
    selectedIds.value.push(id)
  } else {
    selectedIds.value.splice(index, 1)
  }
}

优点:

  • 节省空间: localStorage 存储压力更小。
  • 绝对可靠: 字符串比较不受引用地址影响。
  • 单一数据源: 避免了仓库里的对象属性(如价格、标题)与原始数据源不同步的问题。

4. 总结

这个问题的本质是 JavaScript 对象比较机制与数据持久化存储之间的冲突。刷新页面后,对象引用被重置,导致状态管理失效。

正确的解决方案是:

  1. 避免使用 indexOf 比较对象
  2. 使用唯一标识符(如ID)进行比较
    制与数据持久化存储之间的冲突。刷新页面后,对象引用被重置,导致状态管理失效。

正确的解决方案是:

  1. 避免使用 indexOf 比较对象
  2. 使用唯一标识符(如ID)进行比较
  3. 在状态管理中只存储必要信息(ID)
相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
小羊没烦恼!4 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
前端小万4 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋4 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
伞伞悦读4 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
卡布鲁4 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
C语言小火车4 天前
C/C++ 为什么需要编译器?
开发语言·c++
李少兄4 天前
JavaScript 隐式全局变量解析
javascript