发布时间:2026/8/15 23:50:35
《新项目用 Pinia 还是守 Vuex?一份不废话的选型清单》 关键词技术选型、迁移成本、遗留系统、审计合规、Composition API 团队适配适用场景知乎回答、团队 Wiki、架构评审材料、技术负责人复盘被问Vuex 和 Pinia 选哪个时别急着背Pinia 官方推荐。选型从来不是选最好的是选最贴合当前工程约束的。下面直接给判断矩阵每个观点配真实代码对比。一、闭眼选 Pinia 的 4 种情况1. Vue 3 从零起步的项目官方立场已经锁死新 Vue 3 应用直接用 PiniaVuex 4 不再加新特性。这时候坚持 Vuex 等于主动放弃 TS 推断和 HMR 体验。举例一个简单的计数器模块Vuex 4 写法新项目还要这么写就太累了// store/index.js - Vuex 4 import { createStore } from vuex export default createStore({ state: { count: 0, history: [] }, mutations: { SET_COUNT(state, payload) { state.count payload }, PUSH_HISTORY(state, payload) { state.history.push(payload) } }, actions: { increment({ commit, state }) { const newVal state.count 1 commit(SET_COUNT, newVal) commit(PUSH_HISTORY, 1 → ${newVal}) }, async fetchCount({ commit }) { const res await axios.get(/api/count) commit(SET_COUNT, res.data) } }, getters: { doubleCount: (state) state.count * 2 } })Pinia 写法清爽到不需要注释// stores/counter.js - Pinia import { defineStore } from pinia import axios from axios export const useCounterStore defineStore(counter, () { const count ref(0) const history ref([]) function increment() { count.value history.value.push(1 → ${count.value}) } async function fetchCount() { const res await axios.get(/api/count) count.value res.data } const doubleCount computed(() count.value * 2) return { count, history, increment, fetchCount, doubleCount } })关键差异Vuex改一个 count 要经过 mutation → action 两层异步还得单独处理Pinia直接写函数同步异步一视同仁代码量少 40%2. TypeScript 覆盖率高的工程Pinia 的类型是定义即生效Vuex 的 TS 是配置即劝退。举例用户信息 storeVuex 4 TS 的痛苦日常// store/user.ts - Vuex 4 interface UserState { name: string age: number role: admin | editor | viewer } // 第一步定义类型 type UserStore { state: UserState getters: { userDisplay: (state: UserState) string } mutations: { SET_USER: (state: UserState, payload: PartialUserState) void } actions: { updateUser: (payload: PartialUserState) Promisevoid } } // 第二步手动声明 RootState declare module vue/runtime-core { interface ComponentCustomProperties { $store: Store{ user: UserState } } } // 第三步组件里还得断言 const store useStore() const userName (store.state as any).user?.name // 类型丢失Pinia 的天然推断// stores/user.ts - Pinia import { defineStore } from pinia interface UserState { name: string age: number role: admin | editor | viewer } export const useUserStore defineStore(user, { state: (): UserState ({ name: , age: 0, role: viewer }), getters: { userDisplay(): string { return ${this.name} (${this.role}) // this 自动推断 } }, actions: { async updateUser(payload: PartialUserState) { await api.updateUser(payload) Object.assign(this, payload) // this.$state 也有完整类型 } } }) // 组件里直接用无需任何额外声明 const store useUserStore() console.log(store.name) // string ✓ console.log(store.role) // admin | editor | viewer ✓ store.updateUser({ name: Tom }) // 参数类型校验 ✓实际踩坑案例某团队在 Vuex 项目中有一个user.permissions字段由于 TS 类型定义不到位上线后才发现permissions在某些分支下是undefined导致页面白屏。同样的逻辑用 Piniastate定义时就会强制你处理好初始值和可选类型。3. 重度用 Composition API /script setupPinia 的useXStore()在 setup 里和ref/computed同构。举例一个购物车组件Vuex 在script setup里的别扭写法script setup import { useStore } from vuex import { computed } from vue const store useStore() // 读取状态 - 每次都要通过 store.state const cartItems computed(() store.state.cart.items) const totalPrice computed(() store.state.cart.total) // 触发动作 - 字符串魔法 function addItem(item) { store.dispatch(cart/addItem, item) } function removeItem(id) { store.commit(cart/REMOVE_ITEM, id) } /scriptPinia 的自然写法script setup import { useCartStore } from /stores/cart import { storeToRefs } from pinia const cartStore useCartStore() // 解构出响应式数据 const { items, totalPrice } storeToRefs(cartStore) // 方法直接调用 function addItem(item) { cartStore.addItem(item) } function removeItem(id) { cartStore.removeItem(id) } /script template !-- 直接用不用加 store.state 前缀 -- div v-foritem in items :keyitem.id {{ item.name }} - ¥{{ item.price }} /div p总计¥{{ totalPrice }}/p button clickaddItem(newItem)加入购物车/button /template关键差异Vuexstore.state.cart.items这种三级路径在模板里反复出现且dispatch用字符串容易拼错Pinia就像在用本地refIDE 补全、重构、跳转定义全都支持4. 中大型项目但要扁平分包Pinia 的一个文件一个 store比 Vuex 的 namespaced modules 清爽太多。举例电商后台管理系统涉及商品、订单、用户三个业务域Vuex 的模块嵌套噩梦// store/index.js - Vuex 4 多模块 import { createStore } from vuex import products from ./modules/products import orders from ./modules/orders import users from ./modules/users export default createStore({ modules: { products, // 内部可能还有子模块products.list, products.detail orders, // 子模块orders.list, orders.refund users // 子模块users.profile, users.permissions } }) // 组件里调用时的路径地狱 store.dispatch(products/list/fetchProducts) store.dispatch(orders/refund/submitRefund) store.commit(users/profile/SET_NAME, Tom) // 跨模块引用需要 rootState // orders/store.js 里要拿用户信息 actions: { submitOrder({ rootState }) { const userId rootState.users.profile.id // ... } }Pinia 的扁平结构stores/ ├── productStore.ts // 商品相关 ├── orderStore.ts // 订单相关 └── userStore.ts // 用户相关// stores/productStore.ts - Pinia export const useProductStore defineStore(product, () { const list refProduct[]([]) const detail refProductDetail | null(null) async function fetchList() { /* ... */ } async function fetchDetail(id: string) { /* ... */ } return { list, detail, fetchList, fetchDetail } }) // stores/orderStore.ts - 跨 store 引用 import { useUserStore } from ./userStore export const useOrderStore defineStore(order, () { const userStore useUserStore() async function submitOrder(orderData: Order) { // 直接拿用户信息不用 rootState const userId userStore.profile.id await api.submit({ ...orderData, userId }) } return { submitOrder } })实际效果某中型电商项目从 Vuex 迁到 Pinia 后store 文件数从 12 个含嵌套变成 8 个扁平文件新人理解时间从 2 天缩短到半天。二、继续用 Vuex 不丢人的 3 种情况1. Vue 2 存量系统举例一个运行两年的 OA 系统现有 Vuex store// store/modules/attendance.js - Vuex 3Vue 2 export default { namespaced: true, state: { records: [], todayStatus: {} }, mutations: { SET_RECORDS(state, records) { state.records records }, SET_TODAY_STATUS(state, status) { state.todayStatus status } }, actions: { async fetchRecords({ commit }, date) { const data await api.getAttendance(date) commit(SET_RECORDS, data) } } }如果要迁到 Pinia需要安装vue/composition-apiVue 2 的桥接包安装 pinia注意版本兼容性逐个 store 重写测试回归组件里所有mapState、mapActions替换实际案例某团队花了 3 天评估迁移发现 200 个组件引用了 store最终决定不迁。结论是功能稳定、无性能瓶颈、无新增大需求不动就是最优解。2. 金融/审计类业务强制 mutation 留痕举例资金交易系统每次余额变动必须记录操作人、时间、IPVuex 的 mutation 天然适合做审计日志// store/modules/account.js - Vuex 4 export default { state: { balance: 10000, auditLog: [] }, mutations: { // 所有余额变动必须走这里 UPDATE_BALANCE(state, { amount, operator, ip, reason }) { state.auditLog.push({ before: state.balance, after: state.balance amount, amount, operator, ip, reason, timestamp: Date.now() }) state.balance amount } }, actions: { withdraw({ commit }, { amount, operator, ip }) { // 审核逻辑... commit(UPDATE_BALANCE, { amount: -amount, operator, ip, reason: 提现 }) } } }Pinia 要实现同样效果只能靠约定// stores/account.ts - Pinia export const useAccountStore defineStore(account, () { const balance ref(10000) const auditLog refAuditEntry[]([]) // 全靠自觉每次改 balance 前手动记录日志 function updateBalance(amount: number, meta: AuditMeta) { auditLog.value.push({ before: balance.value, after: balance.value amount, ...meta, timestamp: Date.now() }) balance.value amount } function withdraw(amount: number, meta: AuditMeta) { updateBalance(-amount, { ...meta, reason: 提现 }) } return { balance, auditLog, withdraw } })关键差异Vuexmutation 是唯一的修改入口审计是强制性的Pinia可以绕过updateBalance直接balance.value x审计失效实际案例某支付公司规定所有资金变动必须经过 mutation 审计他们选择继续用 Vuex并且在 CI 阶段加了 lint 规则禁止直接修改 state。3. 深度依赖 Vuex 插件生态举例一套基于 mutation 的埋点系统和权限中间件Vuex 插件示例// plugins/tracker.js - Vuex 插件 export function createTrackerPlugin(options) { return (store) { // 监听所有 mutation store.subscribe((mutation, state) { analytics.track(state_change, { type: mutation.type, payload: mutation.payload, timestamp: Date.now() }) }) } } // plugins/permission.js - 权限拦截 export function createPermissionPlugin(allowedModules) { return (store) { store.subscribeAction((action, state) { if (!allowedModules.includes(action.type.split(/)[0])) { console.warn(Blocked action: ${action.type}) throw new Error(Permission denied) } }) } } // 使用 const store createStore({ plugins: [ createTrackerPlugin(), createPermissionPlugin([products, orders]) ] })Pinia 没有 mutation上述插件机制全部失效。如果要迁移埋点需要在每个 action 里手动调用 analytics权限需要在每个 store 的 action 开头加检查逻辑实际案例某 SaaS 平台有 6 个自定义 Vuex 插件缓存、埋点、权限、错误上报、数据同步、撤销恢复迁移成本估算超过 2 周最终决定等下一个大版本重构时一并处理。三、迁移值不值看边界清晰度中型 Vue 3 项目从 Vuex 迁 Pinia典型节奏是 1–3 天以一个真实的任务管理系统为例迁移前后对比迁移前Vuex// store/modules/tasks.js export default { namespaced: true, state: { list: [], loading: false, filter: all }, mutations: { SET_LIST(state, list) { state.list list }, SET_LOADING(state, v) { state.loading v }, SET_FILTER(state, f) { state.filter f }, ADD_TASK(state, task) { state.list.push(task) }, REMOVE_TASK(state, id) { state.list state.list.filter(t t.id ! id) }, TOGGLE_COMPLETE(state, id) { const task state.list.find(t t.id id) if (task) task.completed !task.completed } }, actions: { async fetchTasks({ commit }) { commit(SET_LOADING, true) const data await api.getTasks() commit(SET_LIST, data) commit(SET_LOADING, false) }, async addTask({ commit }, title) { const task await api.createTask(title) commit(ADD_TASK, task) } }, getters: { filteredTasks(state) { if (state.filter completed) return state.list.filter(t t.completed) if (state.filter active) return state.list.filter(t !t.completed) return state.list }, pendingCount(state) { return state.list.filter(t !t.completed).length } } }迁移后Pinia// stores/tasks.ts export const useTaskStore defineStore(tasks, () { const list refTask[]([]) const loading ref(false) const filter refall | active | completed(all) async function fetchTasks() { loading.value true list.value await api.getTasks() loading.value false } async function addTask(title: string) { const task await api.createTask(title) list.value.push(task) } function removeTask(id: string) { list.value list.value.filter(t t.id ! id) } function toggleComplete(id: string) { const task list.value.find(t t.id id) if (task) task.completed !task.completed } const filteredTasks computed(() { if (filter.value completed) return list.value.filter(t t.completed) if (filter.value active) return list.value.filter(t !t.completed) return list.value }) const pendingCount computed(() list.value.filter(t !t.completed).length) return { list, loading, filter, fetchTasks, addTask, removeTask, toggleComplete, filteredTasks, pendingCount } })迁移信号绿不绿看三点1. store 里 mutation 是不是大多只有一行赋值如果是像上面这个例子6 个 mutation 全是简单赋值Pinia 直接省掉 60% 的样板代码。2. 新人入职要不要讲一小时 Flux 概念某团队统计新人理解 Vuex 的 mutations/actions 区分平均需要 2 天而 Pinia 只需要 30 分钟。如果你的团队有持续的新人 onboarding 压力Pinia 节省的时间非常可观。3. TS 报错是不是长期靠any糊弄比如这个常见场景// Vuex 里常见的摆烂写法 const tasks (store.state as any).tasks.list const user (store.getters as any).userInfo换成 Pinia 后const taskStore useTaskStore() const tasks taskStore.list // Task[] ✓ const userStore useUserStore() const userInfo userStore.userInfo // UserInfo ✓实际案例某团队迁移后TS 相关 bug 减少了 70%因为之前很多运行时错误其实是类型不对导致的。四、一句话收尾Vuex 是把状态关进 Flux 的笼子里Pinia 是把笼子拆了因为 Vue 3 的响应式本身就是笼子。新项目别犹豫Pinia老 Vue 2 别折腾VuexVue 3 老项目看上面那张绿灯表再动手。最后的决策清单你的情况建议新项目Vue 3 TS✅ 闭眼 Pinia新项目Vue 3 JS✅ 依然 Pinia少写一半代码老项目Vue 2❌ 不动除非重构老项目Vue 3mutation 全是赋值✅ 迁1-2天搞定老项目Vue 3有复杂插件/审计需求⚠️ 评估后再动可能不值得团队全是新手✅ Pinia降低学习成本金融/合规要求严格⚠️ Vuex 可能更适合看审计粒度

相关新闻

2026/8/15 23:45:35

输入法词库定制与开发环境术语集成实战指南

你的名字总会被别人说错,而我的输入法里固定了你的名字。这句话听起来像一句浪漫的告白,但在技术人的世界里,它指向一个更实际、更普遍的痛点:如何让计算机“记住”并“理解”那些它不认识的、容易出错的专有名词?无论…

2026/8/15 23:45:35

Java Spring -- AOP详解

一. AOP概念 1.1 什么是 AOP? AOP 的全称是 Aspect-Oriented Programming,即面向切面编程。如果说面向对象编程(OOP)是将系统纵向划分为一个个独立的模块(如用户模块、订单模块),那么AOP 就是将…

2026/8/15 23:45:35

数据集转换技术:核心原理与工业级实践指南

1. 数据集转换的核心价值与应用场景在数据科学和机器学习领域,Dataset_transform(数据集转换)是数据预处理流程中最为关键的环节之一。我处理过上百个真实项目的数据集,90%的模型效果问题都源于数据转换不当。简单来说&#xff0c…

2026/8/16 3:56:14

检测机构查询小程序众多,哪家才是你的最优之选?

检测机构查询小程序推荐榜单 在当今工业自动化快速发展的时代,无论是 PLC控制器、伺服驱动 等设备的生产企业,还是从事 智能制造、工业物联网 相关业务的公司,都经常需要查询检测机构的信息。我在电子电气领域摸爬滚打多年,接触过…

2026/8/16 3:56:14

一家工程机械厂的AI获客30天实录:从零召回到18次被提到

株洲有一家做工程机械零部件外协的工厂,设备投了百来万,干的活不差,客户也稳定。但有个事一直让老板心里不踏实——新客户越来越难找了。项目启动前,团队先做了一次摸底:在四个主流AI工具里,用采购方真实会…

2026/8/16 3:56:14

一次请求的前世今生(进阶):底层原理与生产环境那些坑

一次请求的前世今生(进阶):底层原理与生产环境那些坑 上一篇《Go请求链路(入门)》捋清了主线。这一篇往下挖一层,讲几个生产环境真会踩的坑,以及 c.Next() / c.Abort() 的底层实现。 建议先看完…

2026/8/16 3:56:14

C语言函数递归详解:从核心要素到实战案例

1. 什么是递归 递归是编程中的一种技术&#xff0c;指的是一个函数在其定义内部调用自身。递归一定是依赖于函数的。 史上最简单的递归程序&#xff1a; #include <stdio.h> int main() {printf("hehe\n");main(); //main函数自己调用自己return 0; }这个程序是…

2026/8/16 3:51:13

把心事存进鸿蒙:ArkTS 为日记本设计长文本表与时间戳字段

实例&#xff1a;电子日记本&#xff08;Diary&#xff09;&#xff5c;技术&#xff1a;长文本存储、时间分组查询、关键词搜索一、业务需求分析&#xff1a;日记本的数据形态与记账本有何不同 前三个实例我们处理了「任务清单」&#xff08;短文本状态&#xff09;、「通讯录…

2026/8/16 0:00:35

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/16 0:00:36

工业传感器与变送器详解:序章 从物理世界到工业数据

序章 从物理世界到工业数据 ——重新认识工业传感器与变送器 工业自动化系统正变得日益复杂。今天的工业现场早已不是简单的控制回路,而是由多层技术共同构成的立体体系:PLC、DCS、SCADA、MES、工业互联网、边缘计算与人工智能。控制系统可以执行复杂算法,工业网络可以实现…

2026/8/16 0:00:35

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/16 0:00:36

工业传感器与变送器详解:序章 从物理世界到工业数据

序章 从物理世界到工业数据 ——重新认识工业传感器与变送器 工业自动化系统正变得日益复杂。今天的工业现场早已不是简单的控制回路,而是由多层技术共同构成的立体体系:PLC、DCS、SCADA、MES、工业互联网、边缘计算与人工智能。控制系统可以执行复杂算法,工业网络可以实现…

2026/8/15 9:46:39

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站&#xff0c;核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测&#xff0c;千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队&#xff0c;覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/15 4:56:16

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站&#xff0c;核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测&#xff0c;千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队&#xff0c;覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/15 9:46:30

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具&#xff0c;覆盖选题构思、文献整理、内容生成、格式排版等核心场景&#xff0c;真正帮你高效搞定论文难题。 一、全流程王者&#xff1a;一站式搞定论文全链路&#xff08;一天定稿首…