想象你住进了一栋上百户的公寓大楼。从前没有公共储物间的时候,每家每户都把自己的一套工具、清洁用品、备用灯泡塞满自家的柜子。想借根电钻?你得先去敲6楼小王的门,再到3楼问老李。等借到了,天色都晚了。后来物业在楼下设置了一间“公共储物间”——谁家收到快递、水管漏水、换季存物,都往公共储物间放,家里整洁了,邻里间也不用为了小事楼上楼下跑了。但储物间交给谁管?怎么管?这就出现了两任“管理员”。 Vue 应用其实就是这栋公寓楼,页面上的每个组件就是一个“住户”。组件之间总需要共享数据:用户登录信息、购物车数据、页面主题色……早期大家靠“props 传参”和“事件广播”挨家挨户地吼,数据一多就乱成一团。于是 Vue 生态出现了两任“公共储物间管理员”:老管家 Vuex,年轻管家 Pinia。本文就用这个“公寓储物间”的故事,带你从零搞懂它们的设计思想、Store 结构、响应式原理和最佳实践,学会在大型 Vue 项目里优雅地管理全局状态。

岗位关联

状态管理是 Vue 大型项目开发的基石技能。掌握 Vuex/Pinia 的对比与选型,不仅是简历加分项,更是你在团队中承担核心开发、主导架构设计的基本功。

目录
  1. 可视化演示
  2. 状态管理基础
  3. Vuex设计思想
  4. Pinia设计思想
  5. Store结构设计
  6. 响应式原理
  7. 持久化调试
  8. 选型实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Vue状态管理核心概念:Prop Drilling问题vs集中管理、Pinia Store三件套(State/Getters/Actions)结构、以及Pinia与Vuex的关键差异对比和状态持久化。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+captions.flow+'

状态管理基础

公寓楼刚建好的时候,物业还没设立公共储物间。一楼住户要修水管,发现工具在自己家;六楼的电工来修电路,发现电钻在二楼。各家各户的东西都堆在自己房间里,借个工具要跑遍整栋楼。Vue 应用也一样,页面里导航栏、内容区、侧边栏、弹窗……常常需要共享同一份数据:用户登录状态、购物车数量、当前主题色。如果没有状态管理库,开发者只能用 props 一层层往下传,或通过事件往上派发,组件一多就组成了“蜘蛛网”。

假设有 n 个组件需要互相通信,直接连接的方式会产生 C(n,2) 条链路。5 个组件就是 10 条,10 个组件就是 45 条。维护这样一张网,改一处状态要顺着链路追溯半天,Bug 概率呈指数上升。状态管理库的解法很简单——把共享数据从组件中抽离,集中放进一个 Store 对象,组件与 Store 一对一连接,连接数从 C(n,2) 降到 n 条。这就是“全局状态共享 + 单向数据流”的思想。

有了公共储物间后,每家只需一条路线通向储物间,不用再跑遍全楼。组件共享数据也从“全楼喊话”变成“去储物间存取”,这就是状态管理的初心。

C(n,2)=n(n−1)2C(n, 2) = \frac{n(n-1)}{2}
这是组合数公式,用来量化“组件两两之间直接通信”所需维护的连接数量。n 越大,连接数越可怕。
  1. n 组件总数n 表示需要共享数据的组件总个数,例如页面中有 5 个组件都依赖购物车数据。
    n=5n = 5
  2. C(n, 2) 连接组合数从 n 个组件中任意选出 2 个建立直接通信连接的组合方式总量。
    C(5,2)=(52)=10C(5, 2) = \binom{5}{2} = 10
  3. n(n-1) 有向连接数每个组件都能向其他 n-1 个组件发消息,得到有向连接总数。
    5×4=205 \times 4 = 20
  4. 除以 2 去除重复A 到 B 与 B 到 A 是同一条通信链路,除以 2 后才是真实链路数。
    202=10\frac{20}{2} = 10

Vuex设计思想

老物业成立了“公共储物间管理办公室”——想用储物间的东西,必须填写《物品申请表》(Mutation),而且申请表必须在值班窗口当场办理(同步)。如果是远距离调货,得先打电话给物业接头人(Action),接头人确认货源后,再填写申请表让仓库管理员取货。Vuex 是 Vue 2 时代的官方状态管理库,核心是“单一数据源 + 单向数据流”。Store 里有四个核心成员:State 是唯一的信息源;Getter 类似 computed,从 State 派生只读数据;Mutation 是唯一能修改 State 的通道,且必须是同步函数;Action 处理异步业务逻辑,完成后通过 commit 提交 Mutation。

Vuex 的严格流程可以总结为一条公式:组件 dispatch 触发 Action,Action 执行业务后 commit Mutation,Mutation 同步修改 State,State 变化后所有依赖组件自动更新。这套设计保证了数据变更全部有迹可循,就像物业要求每次取货都必须有书面申请一样。

Vuex 的“严格审批流程”带来了极强的可预测性,任何状态变化都能在 Mutation 记录里找到“责任签字”。代价是写码繁琐,就像老物业的表格一样层层备案,让开发者在大型严谨项目中感到安心。

S′=mutation(S,payload)S' = mutation(S, payload)
这是 Vuex 状态更新的核心等式:传入旧状态 S 和载荷 payload,经过 mutation 函数处理后,得到新状态 S'。
  1. S 旧状态当前 State 对象,存储应用所有公共数据,类似储物间的库存台账。
    S={count:0,user:null}S = \{count: 0, user: null\}
  2. mutation 修改函数Vuex 中开发者定义的纯同步函数,接收旧状态与载荷并修改状态。
    mutation(S,payload)mutation(S, payload)
  3. payload 载荷参数组件调用 commit 时传入的数据对象,如加购请求中的商品 ID。
    payload={productId:′A001′,count:2}payload = \{productId: 'A001', count: 2\}
  4. S' 新状态mutation 执行完成后的最新状态,驱动所有关联组件重新渲染。
    S′=mutation(S,payload)S' = mutation(S, payload)

Pinia设计思想

小区新换了一位年轻管家 Pinia,他宣布:公共储物间不再需要纸质申请表,每家每户直接扫码进门,看到需要的东西直接拿走(直接改 state);如果要代购复杂物品,也可以跟他说一声(Action),他帮你统一处理。Pinia 是 Vue 官方推出的新一代状态管理库,在 Vue 3 中默认推荐使用。核心设计思想是“极简 + Composition 化”:State 仍是数据仓库,但支持直接读写;Getter 与 Vuex 相同;Actions 把 Vuex 中"Action + Mutation"合并为一个,同步异步都可写;通过 defineStore 一行定义,无需 modules 嵌套。

Pinia 的定义非常直观,一个 Store 就是一组状态加一组方法。内部的 state 被 Vue 的 reactive() 包裹成响应式对象,所以直接赋值 store.count = 5 也会触发组件更新。它还天然支持 Composition API:在 setup 里调用 useCounterStore(),就能以变量形式访问所有能力。

Pinia 就像是年轻管家推行的“去流程化”改革。省去的表格与审批换来了开发效率,同时保留了“所有东西都在储物间”的集中管理优势。记住:Vuex 管得严但有安全感,Pinia 省得事但自由度更高。

store=defineStore(id,options)store = defineStore(id, options)
这是 Pinia 定义 Store 的工厂函数格式:给每个储物间一个唯一门牌号 id,再配上配置对象 options,就能得到可用的 Store 实例。
  1. id 唯一标识Store 的全局唯一 ID,相当于储物间的门牌号,禁止重复。
    id=′cartStore′id = 'cartStore'
  2. options 配置对象包含 state、getters、actions 三项,定义数据的读写行为。
    options={state:()=>({...}),getters:{...},actions:{...}}options = \{state: () => (\{...\}), getters: \{...\}, actions: \{...\}\}
  3. defineStore 注册工厂Pinia 提供的注册函数,根据 id 与配置生成一个 useXxxStore 函数。
  4. store 实例对象组件中调用 useXxxStore() 后得到的 Store 实例,可直接解构使用。

Store结构设计

储物间刚开张时,所有物品都堆在同一个大货架上:电钻旁边放拖把,拖把旁边摞着棉被。住户找东西找得满头大汗。后来老管家按面积把储物间分成工具区、清洁区、换季衣物区,每个区挂上牌子,住户按图索骥。Store 结构设计也是一样的道理——如果所有状态都堆在一个 Store 里,项目变大后一个 Store 的代码量可能上千行。管理上需要按业务域拆分模块:Vuex 中通过 modules 实现,每个子模块有自己的 State、Getters、Mutations、Actions,并可以开启 namespaced: true 形成独立命名空间。

具体实践中,模块化拆分按业务功能(用户、购物车、订单)拆成独立小 Store;命名空间让提交时自动加前缀,避免跨模块命名冲突;职责划分把状态分为 UI 状态、领域状态、应用状态;而在 Pinia 中,为了保持解构后的响应性,必须用 storeToRefs(store) 包裹再解构。模块化后的 Store 是多个子模块的并集:

储物间的分区牌就是“命名空间”,按域划分的储物格就是“模块化拆分”。你的代码结构越清晰,团队协作效率越高——没人愿意在堆满杂物的储物间里翻找一把螺丝刀。

Store=⋃i=1nModuleiStore = \bigcup_{i=1}^{n} Module_i
并集公式表示:一个完整的 Store 是由多个子模块组合而成的,模块越多,职责越清晰,但也要避免过度拆分。
  1. Module_i 第 i 个模块代表被拆分的某个功能模块,如用户模块、购物车模块。
    Module1=User,Module2=CartModule_1 = User, Module_2 = Cart
  2. ⋃ 并集运算Store 的最终形态是各模块的并集,联合后对外提供统一访问入口。
    Store=User∪Cart∪OrderStore = User \cup Cart \cup Order
  3. i 从 1 到 n 枚举n 是模块总数,工程上推荐 5~15 个模块较为合理,不宜过碎或过粗。
    n=∣Modules∣n = |Modules|

响应式原理

新储物间装了“智能追踪系统”:住户从储物柜取走某件物品时,系统自动记录“谁在用什么”。假如螺丝刀被升级换代、尺寸更人性化了,系统会立刻给所有曾借过螺丝刀的人发通知。Vue 3 / Pinia 的响应式原理与此高度相似——Pinia 的 state 底层就是一个 Vue reactive() 包裹的响应式对象,内部使用 Proxy 拦截对对象属性的读取和写入操作。读取时执行 track 收集依赖,写入时执行 trigger 触发更新。

Proxy 代理的是整个 state 对象,任何属性读写都会被拦截;track 在组件使用某个 state 属性时,把当前组件的渲染 effect 记入该属性的依赖列表;trigger 在属性被修改时,遍历依赖列表,依次执行 effect 通知组件重新渲染。这条链路是 Vue 生态响应式魔法的心脏:

智能追踪系统的“谁在用”登记表就是 track,“工具升级时挨个打电话”就是 trigger。响应式让你不必手动操作 DOM 就能获得“自动通知”的能力,这也是 Vue 生态最迷人的底层魔法。

track(target,key)→记录依赖trigger(target,key)→通知effectstrack(target, key) \xrightarrow[]{记录依赖} trigger(target, key) \xrightarrow[]{通知} effects
两步走公式:读取时 track 记下谁在用,修改时 trigger 挨个通知。就像储物间系统记下借用人名单,工具更新时逐个打电话。
  1. target 响应式目标对象被 Proxy 代理的 state 对象,所有状态都挂在这里。
    target=store.statetarget = store.state
  2. key 状态键名组件读取的具体属性名,比如 count、user、items。
    key=′count′key = 'count'
  3. track 收集依赖属性被读取时执行,将该属性与当前组件的渲染 effect 建立关联。
  4. trigger 触发更新属性被修改时执行,取出该属性的依赖列表并逐一通知执行。
  5. effects 副作用队列所有关联组件的更新函数集合,被 trigger 调用后完成界面刷新。
    effects={fn1,fn2,…,fnk}effects = \{fn_1, fn_2, \dots, fn_k\}

持久化调试

储物间系统最怕断电——住户存取记录全存在本地电脑里,万一物业服务器重启,所有人的借用记录都没了。于是物业增加了“离线备份册”(持久化存储)。同时监控室里的大屏(DevTools)能像看监控回放一样,看到每一次操作的细节,甚至可以“倒带回放”。状态管理中的“持久化”指的是把 Store 的 State 序列化到 localStorage 或 sessionStorage 中,页面刷新后自动恢复;调试能力则依赖 Vue DevTools,它能展示每次 Mutation 提交的时间戳、名称、前后 State 快照,支持“时间旅行”调试。

会话持久化利用 localStorage(跨会话保留)或 sessionStorage(关闭页面即失效)保存 State;DevTools 面板的 Vuex/Pinia 标签页逐条展示每次提交;时间旅行点击某历史记录后,store 的 State 被替换为该快照,界面同步回退;插件机制则通过 Vuex 的 subscribe 和 Pinia 的 $subscribe 实现自定义存储与上报。持久化本质是一个序列化-反序列化过程:

离线备份册让断电不心慌,监控回放让任何状态修改都能追溯到人。工程级状态管理,不仅需要“好用的储物间”,还要“可靠的账本”和“清晰的监控”。

series:storage.setItem(key,JSON.stringify(state))⇒state=JSON.parse(storage.getItem(key))series: \quad storage.setItem(key, \text{JSON.stringify}(state)) \quad\Rightarrow\quad state = \text{JSON.parse}(storage.getItem(key))
序列化公式:先把对象变成字符串存进本地,再用时取回字符串解析成对象,就像把储物间的物品登记成账本、按账本恢复取物。
  1. serialize 序列化将 JS 对象转换为 JSON 字符串,便于存入 localStorage。
    JSON.stringify(state)\text{JSON.stringify}(state)
  2. storage.setItem 写入把序列化结果按 key 写入浏览器本地存储。
    storage.setItem(key,serialized)storage.setItem(key, serialized)
  3. storage.getItem 读取页面加载时从本地存储按 key 取回字符串。
    storage.getItem(key)storage.getItem(key)
  4. parse 反序列化把字符串解析回 JS 对象并重新赋给 Store 的 State。
    JSON.parse(rawString)\text{JSON.parse}(rawString)

选型实践

老物业的严格管控制度虽然繁琐,但对大型老小区(旧项目)来说已经很成熟,住户也习惯了。年轻管家推行的新制度简单高效,适合新小区(新项目),但部分住户还在观望。到底选谁,要看你们小区的实际情况。Vuex 与 Pinia 不是简单的“新与旧”,而是两种设计哲学的取舍。Vuex 4 与 Pinia 在 Vue 3 项目里都可以稳定使用,选型时可以围绕“代码规范度、开发效率、生态成熟度”三个维度打分。

Vuex 适用大型老项目、团队已有大量 Vuex 模块、对 Mutation 强约束有依赖的场景;Pinia 适用 Vue 3 新项目、中小型项目、追求高效开发与 TypeScript 类型的场景。迁移策略推荐渐进式——新模块用 Pinia,老模块继续用 Vuex,两者可共存。团队规范方面,无论选哪个,都要约定 Store 命名(userStore)、状态更新方式、模块边界。综合评分公式如下:

选型没有绝对答案,就像物业管理也没有“哪套最好”,只有“哪套最适合你们小区”。做决策前,先统计一下:住户(团队)习惯了什么?楼龄(项目代码库)有多大?未来住户(新功能)增长有多快?

Score=0.4×规范+0.3×效率+0.3×生态Score = 0.4 \times 规范 + 0.3 \times 效率 + 0.3 \times 生态
加权评分公式:规范、效率、生态三个维度按 0.4/0.3/0.3 的权重加权求和,得到选型综合得分。
  1. 规范 团队规范性衡量库强制约束力对代码规范的影响,Vuex 更强,Pinia 更灵活。
  2. 效率 开发效率衡量模板代码量与上手难度,Pinia 明显更简洁。
  3. 生态 生态成熟度衡量社区插件、教程、招聘市场比例,Vuex 存量生态庞大,Pinia 增长迅速。
  4. 0.4/0.3/0.3 权重比代表多数人心中三者的相对重要度,即“规范为先、效率与生态并重”。
    Score=0.4×8+0.3×9+0.3×7=8.0Score = 0.4 \times 8 + 0.3 \times 9 + 0.3 \times 7 = 8.0

AI 应用场景

AI聊天助手前端的状态流管理

阶段:前端工程化 / 状态编排

解决的问题:聊天界面涉及消息列表、输入框、会话切换、流式输出等多个组件。没有统一 Store 时,组件间通过事件互相通知,代码像蜘蛛网一样难以维护。用 Pinia 集中管理会话 ID、消息数组、连接状态、正在生成的 token,组件通过 Store 读写,逻辑清晰。

具体案例:某公司的智能客服 Web 端,用户点击“历史会话”侧边栏、聊天窗口、底部输入框三个组件共享“当前会话 ID”“消息列表”“模型加载状态”。用 Pinia 的 chatStore 统一管理:侧边栏切换会话时,chatStore 更新 currentSessionId,聊天窗口的 watch 响应并自动加载消息;流式输出时,store.loading = true,token 累积追加,多个组件同时同步进度。

AI 绘画平台的生成任务状态机

阶段:前端状态机设计

解决的问题:AI 绘画前端需要展示“输入提示词→排队→生成中→成功/失败”的完整流程,多个组件(参数面板、进度条、图片网格、历史记录)都要感知这个状态。传统做法中组件的 data 各自为政,状态一变就不同步。

具体案例:一个 AI 绘画 Web 应用使用 Vuex 模块化设计:taskModule 管理任务队列和每个生成任务的状态机。点击“生成”按钮,Action 触发异步请求,期间 commit 更新 task.status;网格组件根据 status 展示不同占位样式;队列模块存有任务 ID 顺序,实现多次提交不卡顿。每次 Mutation 都有快照,DevTools 时间旅行可以回放到任意生成阶段排查问题。

数据标注平台的多角色协同状态

阶段:多端协作数据同步

解决的问题:标注平台中,标注员、审核员、管理员看到的界面不同,但操作的是同一份任务数据。用 Pinia 按角色拆分 Store:标注员 Store 管理当前标注进度,审核员 Store 管理审核流,管理员 Store 做全局统计,Store 间通过组合式 API 互相引用,实现权限隔离与数据联动。

具体案例:某医疗影像标注平台,标注员切换到下一张切片时,labelStore.currentIndex 变化,画布组件自动加载新切片;标注框数据保存到 annotations 数组,审核页面通过共享 Store 实时显示新增标注,无需刷新页面。团队新增“临时保存草稿”需求时,只需在 Store actions 里加一个 draft 方法即可。

Python 演示

python71 行
class Store:    """简化版状态管理:模拟 Vuex/Pinia 的集中式状态管理"""     def __init__(self, state):        self._state = dict(state)        self._listeners = []     def subscribe(self, callback):        self._listeners.append(callback)     def _notify(self, key, old_value, new_value):        for cb in self._listeners:            cb(key, old_value, new_value)     def get(self, key):        return self._state.get(key)     def set(self, key, value):        old = self._state.get(key)        if old != value:            self._state[key] = value            self._notify(key, old, value)     @property    def state(self):        return dict(self._state)  class VuexStore(Store):    """模拟 Vuex: 只能通过 commit 修改状态"""     def __init__(self, state, mutations):        super().__init__(state)        self._mutations = mutations     def commit(self, name, payload=None):        if name not in self._mutations:            raise KeyError(f"[Vuex] 未定义的 mutation: {name}")        old = dict(self._state)        self._mutations[name](self._state, payload)        for key in self._state:            if old.get(key) != self._state.get(key):                self._notify(key, old.get(key), self._state.get(key))  class PiniaStore(Store):    """模拟 Pinia: 直接修改 state 即可"""     def __init__(self, store_id, state):        super().__init__(state)        self._store_id = store_id     def patch(self, partial):        for key, value in partial.items():            self.set(key, value)  # ---- 使用示例 ----def increment(state, payload):    state['count'] = state.get('count', 0) + payload  vuex = VuexStore({'count': 0}, {'increment': increment})vuex.subscribe(lambda k, o, n: print(f'[Vuex] {k}: {o} -> {n}'))vuex.commit('increment', 5)vuex.commit('increment', 3) pinia = PiniaStore('counter', {'count': 0})pinia.subscribe(lambda k, o, n: print(f'[Pinia] {k}: {o} -> {n}'))pinia.patch({'count': 10})pinia.patch({'count': 10, 'user': 'alice'})
目的:用 Python 模拟 Vuex 的 commit 写法和 Pinia 的直接改 state 写法,直观对比两种状态管理模式的核心差异。
  1. self._listeners.append(callback)保存订阅回调,这是“观察者模式”的注册动作,对应组件监听 Store 变化。
  2. def commit(self, name, payload=None)模拟 Vuex 唯一的修改入口,所有更新必须先调 commit,对应严格审批制度。
  3. if name not in self._mutations:必须提前注册 mutation 才能调用,否则抛异常,对应 Vuex 严格校验。
  4. old = dict(self._state)快照旧状态,用于对比哪些字段发生了变更,类似 Mutation 前后的记录。
  5. self._mutations[name](self._state, payload)执行真正的状态修改函数,类似 Vuex 内部调用 mutation。
  6. def patch(self, partial):模拟 Pinia 的 $patch 批量更新,直接传入部分状态即可完成修改。

常见误区

误区我刚学状态管理,觉得共享变量越多越好,于是把组件内部的临时 UI 状态、表单草稿、甚至按钮的加载动画都放进 Store 里。反正统一管理嘛!

正解State 里放太多无关数据会让 Store 难以维护,相当于把储物间当垃圾房。核心原则是:只有需要被多个组件共享、且跨页面/跨会话的状态才放入 Store。像表单输入框的局部草稿、弹窗开关等仅组件内使用的临时状态,应保留在组件内部用 ref/reactive 管理。你可以把“是否被多个组件使用”作为一个投票门槛,不达标的就留在组件里。

为何常见:新手对“全局状态”有过度理解,觉得“既然学了状态管理,就应该把所有状态都全局化”,加上很多教程默认把所有数据都放在 Store 示例中,造成了误导。

误区我想让逻辑简单一点,把 API 请求直接写进 mutation 里,这样组件就不用 dispatch 了。

正解Vuex 规定 mutation 必须是同步函数,这样 devtools 才能精确追踪每次 State 变化的时间和来源。如果 mutation 里写异步(比如 setTimeout 或 axios),devtools 无法捕获异步回调里的状态变更,时间旅行调试就失效了。正确做法是:异步操作放 Action,完成后通过 commit 提交 mutation。

为何常见:从效果上看,异步 mutation 也能完成状态更新,很多开发者只看到“能跑”,忽视了 devtools 调试的可靠性。

误区Pinia 的 store 返回的是响应式对象,那我直接 `const { count, user } = store` 解构出来用,应该也是响应式的吧?

正解直接解构会丢失代理对象的响应式连接,count 变成普通值,修改它不会触发组件更新。需要使用 storeToRefs 对 state/getters 解构才能保持响应性。对 actions 方法可以直接解构,因为方法不依赖代理的响应式追踪。

为何常见:useStore() 返回的 store 看起来就是个普通对象,很容易和 reactive 的隐式解构判断混淆,不知道内部 Proxy 的读写陷阱。

误区听说 Pinia 是官方推荐的,所以 Vuex 活着的时间不长了,我得赶紧把老项目全部重写。

正解Vuex 4 依然官方维护,适合既有代码库的迭代;Pinia 是官方新推荐的名字,但两者可以共存。在 Vue 3 项目中可以渐进式引入:新模块用 Pinia、老模块继续用 Vuex,避免大规模回归。Vuex 也没有被废弃,其单向数据流思想仍指导着 Pinia 的设计。

为何常见:Vue 团队把 Pinia 写进官方文档后,社区一度出现“Vuex 已死”的言论,让人误以为要全面替换。实际上渐进式迁移才是工程界共识。

面试题

Q1. Vuex 中 Mutation 与 Action 的核心区别是什么?

单选题 · easy

  • A.Mutation 可放异步逻辑,Action 只能同步
  • B.Mutation 同步改 State,Action 处理异步后提交 Mutation← 正确答案
  • C.Action 可直接改 State,Mutation 身份可省略
  • D.Mutation 与 Action 功能完全相同,无任何区别

答案:B。Vuex 设计单向数据流:Action 负责异步逻辑,完成后 commit 触发 Mutation;只有 Mutation 是唯一允许同步修改 State 的途径。这就像物业接头人(Action)确认货源后,仓库管理员(Mutation)才能动货架。

Q2. Pinia 相对 Vuex 的主要改进包括哪些?

多选题 · mid

  • A.移除了 Mutation 概念,状态直接可写← 正确答案
  • B.强制所有状态必须使用嵌套 modules
  • C.更好的 TypeScript 类型推断支持← 正确答案
  • D.必须使用 Composition API 才能书写

答案:AC。Pinia 去掉了 Mutation 和 Action 的区分,统一为 actions;state 可直接赋值修改;类型推断更友好。但它不强制使用 Composition API,Options 写法也支持,且没有强制嵌套 modules。

Q3. Vuex 中的 Getter 对应哪种常见编程概念?

单选题 · easy

  • A.全局变量,随处可改
  • B.计算属性,从 State 派生只读数据← 正确答案
  • C.事件监听器,接收外部消息
  • D.定时器,周期触发更新

答案:B。Getter 类似 Vue 的 computed,从 State 中派生出格式化数据,不修改原始状态。就像储物间的“资产盘点表”——没有多放东西,而是在表上计算出物品总价。

Q4. 有 8 个组件需要两两直接通信,连接数为 C(8,2),结果是多少?

单选题 · mid

  • A.16 条连接,恰好是组件数的两倍
  • B.28 条连接,维护成本非常高← 正确答案
  • C.32 条连接,与组件数成正比
  • D.64 条连接,等于 n 的平方

答案:B。C(8,2) = 8×7÷2 = 28。8 个组件直接两两通信需要 28 条连接,引入 Store 后降为 8 条(每组件只连 Store),代码可维护性大幅提升。

Q5. 关于 Pinia 的 storeToRefs,以下说法正确的有哪些?

多选题 · mid

  • A.只能对 Options Store 使用该函数
  • B.能保持解构后状态的响应性← 正确答案
  • C.actions 方法可直接解构使用,无需包裹← 正确答案
  • D.storeToRefs 会创建一个新 Store 实例

答案:BC。storeToRefs 是将 store 的响应式属性转为 ref 的工具,保持解构后响应式;普通 actions 方法解构后 this 绑定不丢失,可直接使用;它适用于所有 Pinia Store 类型,不会创建新 Store。

Q6. 在 Vuex 严格模式下,mutation 中执行 setTimeout 异步修改 State 会发生什么?

单选题 · hard

  • A.一切正常,DevTools 如实记录每次修改
  • B.DevTools 控制台提示不得在此处修改状态← 正确答案
  • C.应用直接白屏,抛出致命 RuntimeError
  • D.State 永远不会发生变化,静默失败

答案:B。严格模式下 Vuex 用 $watch 监听 State 变化,异步回调中修改 State 会触发"do not mutate vuex store state outside mutation handlers"的错误提示。State 本身会更新,但 DevTools 无法准确记录该变更,时间旅行调试失效。

Q7. Vue 3 响应式系统底层依赖的是什么机制?

单选题 · easy

  • A.Object.defineProperty 遍历属性劫持
  • B.Proxy 代理整个对象拦截读写操作← 正确答案
  • C.MutationObserver 监听 DOM 变化
  • D.Web Workers 后台线程同步数据

答案:B。Vue 3 使用 Proxy 代理整个 state 对象,任何属性读写都会被拦截,动态新增属性也能响应式。Object.defineProperty 是 Vue 2 的旧实现,需要提前遍历属性且无法监听新增属性。

Q8. 关于 Pinia 的 Store 结构,下列说法正确的有哪些?

多选题 · hard

  • A.不同 Store 之间可以互相引用调用← 正确答案
  • B.每个 Store 必须有全局唯一的 id← 正确答案
  • C.Store 的 state 可以直接赋值修改← 正确答案
  • D.必须在 src/store 目录下创建才生效

答案:ABC。Pinia 的 Store 之间可以互相引入(组合式思想);定义时 id 必须全局唯一;state 底层是 reactive,直接赋值即可触发更新。文件位置没有硬性要求,只要在 setup 中正确 use 即可。

Q9. Vuex 响应式系统中,track 函数的核心职责是什么?

单选题 · hard

  • A.拦截用户的键盘输入并生成日志
  • B.记录依赖某个 State 字段的组件渲染 effect← 正确答案
  • C.定期清理未使用的 State 变量释放内存
  • D.把 State 序列化为 JSON 字符串持久化保存

答案:B。track 是依赖收集阶段的核心,发生在 State 属性被读取时,将该属性与当前组件的渲染 effect 建立关联。当属性被修改,trigger 会通知这些关联 effect 重新执行,实现界面自动更新。

Q10. 大型项目从 Vuex 迁移到 Pinia,最推荐的策略是什么?

单选题 · mid

  • A.暂停业务开发,一次性全量重写所有模块
  • B.老模块保留 Vuex,新模块用 Pinia,渐进迁移← 正确答案
  • C.开发自动翻译工具,一键转换所有 Vuex 代码
  • D.让团队成员自由选择,想用哪个就用哪个

答案:B。最佳实践是渐进式迁移:老模块继续使用 Vuex,新模块直接用 Pinia,两者在 Vue 3 项目中可以共存。等老模块迭代时再顺手迁移,把回归风险降到最低。一键转换工具不现实,自由选择会造成风格混乱。