0%

踩雷!pnpm10 升级后 Vue2.7+Pinia 要多写.value?根源 + 终极解

踩雷!pnpm10 升级后 Vue2.7+Pinia 要多写.value?根源 + 终极解

本文适配 Hexo,含 Front-matter。掘金版请见仓库 juejin-articles。

1. 痛点复盘:一升级就“全员 .value”

  • Vue2.7 项目将包管理器从 pnpm8 → pnpm10 后,Pinia 的所有 state/getters 使用处突然都需要 .value 才能工作;
  • 升级前(pnpm8)一切正常;
  • 本地 node_modules 下的 pinia 没有任何 “vue2” 专属文件或入口(找不到 vue2 相关目录),
  • 尝试过:修改 .npmrc、Pinia 降级、封装代理 等均无效。

结论:问题不在 Pinia 写法,而在包解析与入口选择

2. 根源通俗解析:vue-demi 被 pnpm10“带偏了”

  • Pinia 依赖 vue-demi 来判断当前项目到底是 Vue2 还是 Vue3,然后返回对应版本的 API。
  • pnpm10 的解析规则下,vue-demi 误判当前是 Vue3 环境,于是返回了 Vue3 版本的 API
    • Vue3 中 ref 不会自动在模板/计算中解包,导致使用时需要 .value
    • 这就解释了为何升级后你在 Vue2.7 + Pinia 项目里到处都要写 .value
  • 升级前(pnpm8)因为解析差异,vue-demi 正常识别为 Vue2.7,因此**无需 .value**。

一句话:不是 Pinia 变了,是 vue-demi 在 pnpm10 下选错了入口。

3. 终极实操解:Vite 别名强绑 vue-demi → Vue2.7 专属入口

目标:强制让所有依赖使用 vue-demi 的 Vue2.7 入口,彻底修正误判。

关键思路:通过 Vite 的 resolve.alias,把 vue-demi 指向 Vue2.7 专属 build,让 Pinia 以及其它依赖都拿到正确的 API。

vite.config.js 完整片段(可复制)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue2'
import path from 'path'

export default defineConfig({
plugins: [vue()],
resolve: {
alias: [
// 1) 强制绑定 vue 到项目使用的 Vue2.7
{ find: 'vue', replacement: path.resolve(__dirname, 'node_modules/vue') },
// 2) 强制将 vue-demi 指向 Vue2.7 专属入口
// 注意:不同版本的 vue-demi 目录结构可能略有差异,核心是选中 "compat for Vue 2.7" 的构建产物
{
find: 'vue-demi',
replacement: path.resolve(
__dirname,
'node_modules/vue-demi/lib/index.iife.js' // 示例路径:Vue2.7 兼容构建入口
)
}
]
}
})

路径说明

  • node_modules/vue-demi/lib/index.iife.js 为示例路径,请在你的项目中确认 vue-demi 包内对 Vue2.7 兼容的构建文件位置:
    • 常见还包括:lib/index-v2.jslib/v2/index.mjslib/index.cjs 等;
    • 只要该入口在源码中明确走 Vue2 分支即可(可打开文件搜 isVue2 标识)。

提示:如果你使用的是 monorepo 或者有多层 node_modules,务必确保 alias 指向的是项目最终运行时实际被解析的那一个路径

4. 验证:无需 .value 的 Pinia 使用示例

重启本地服务后,随手在组件中验证:

1
2
3
4
5
6
7
8
9
10
11
// stores/counter.ts
import { defineStore } from 'pinia'

export const useCounter = defineStore('counter', {
state: () => ({
n: 1
}),
getters: {
double: (state) => state.n * 2
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- components/Demo.vue -->
<template>
<div>
<p>n: {{ counter.n }}</p>
<p>double: {{ counter.double }}</p>
</div>
</template>

<script>
import { useCounter } from '@/stores/counter'
export default {
setup() {
const counter = useCounter()
// 这里不需要写 counter.n.value / counter.double.value
return { counter }
}
}
</script>

现象:模板中直接使用 counter.ncounter.double 正常工作,无需 .value

5. 避坑说明:常规方案为什么不靠谱?

  • .npmrc / 换解析策略:pnpm10 的整体解析行为变化较大,仅凭 npmrc 配置很难稳定影响到 vue-demi 的入口选择;
  • Pinia 降级:治标不治本,且可能带来其它依赖的兼容性问题;
  • 封装代理(自己包装 .value:不仅侵入业务,还会牺牲可读性和可维护性。

本方案优势

  • 一招根治:直击根因(入口判断),从源头纠偏;
  • 零业务代码修改:无需改任何组件或 store 代码;
  • 不降级:保留 pnpm10 带来的性能和生态优势;
  • 无副作用:只影响 vue-demi 的解析位置,不改变其它模块行为。

6. 拓展:vue-demi 与 pnpm9+/10 的解析变化

  • vue-demi:为同时兼容 Vue2 & Vue3 的库提供统一 API 封装,它会在安装/运行时根据环境决定导出哪个版本的实现;
  • pnpm9+/10:包解析(尤其是依赖的入口与 peer 解析)更加严格与模块化,一些“隐式”依赖路径在旧版本下可用,升级后会触发分支错误。

因此在 Vue2.7 + Pinia 的组合下,手动通过 alias 锁定 vue-demi 的 Vue2.7 入口是最稳妥的工程化处理。


核心总结 + 避坑提示

  • 升级 pnpm10 导致 vue-demi 误判为 Vue3 → Pinia 返回 Vue3 API → **必须 .value**;
  • 通过 Vite resolve.alias 强制把 vue-demi 指向 Vue2.7 兼容入口,即可恢复原有行为;
  • 发布前务必 清空缓存并重启rm -rf node_modules && pnpm ipnpm dev),确认生效;
  • 避坑:不要靠随意降级或 Hack 业务代码,工程化锁定入口是终极解。