当前位置: 首页 > news >正文

Pinia 两种写法全解析:Options Store vs Setup Store(含实践与场景对比)

目标:把 Pinia 的两种写法讲透,写明“怎么写、怎么用、怎么选、各自优缺点与典型场景”。全文配完整代码与注意事项,可直接当团队规范参考。


一、背景与准备

  • 适用版本:Vue 3 + Pinia 2.x
  • 安装与初始化:
# 安装
npm i pinia# main.ts/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const app = createApp(App)
app.use(createPinia())
app.mount('#app')

Pinia 提供两种定义 Store 的方式:

  • Options Store(配置式):写法类似 Vuex,结构清晰,学习成本低。
  • Setup Store(组合式):写法与 Composition API 一致,灵活可复用,能直接使用 refcomputedwatch、自定义 composable。

下面分别实现 同一个业务:计数器 + 异步拉取用户信息,用两种写法各做一遍,再对比差异与使用场景。


二、Options Store(配置式)

2.1 定义

// stores/counter.ts
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {// 1) state:必须是函数,返回对象;可被 DevTools 追踪、可序列化state: () => ({count: 0,user: null as null | { id: number; name: string }}),// 2) getters:类似计算属性,支持缓存与依赖追踪getters: {double: (state) => state.count * 2,welcome(state) {return state.user ? `Hi, ${state.user.name}` : 'Guest'}},// 3) actions:业务方法,支持异步;这里的 this 指向 store 实例actions: {increment() {this.count++},reset() {this.$reset()},async fetchUser() {// 模拟请求await new Promise((r) => setTimeout(r, 400))this.user = { id: 1, name: 'Tom' }}}
})

注意:在 actions 里不要用箭头函数,否则 this 不指向 store;如果必须用箭头函数,改为显式引用 useCounterStore() 返回的实例。

2.2 组件中使用

<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'const counter = useCounterStore()
// 解构 state/getters 请用 storeToRefs,保持解构后的值仍具备响应性
const { count, double, welcome, user } = storeToRefs(counter)function add() {counter.increment()
}function load() {counter.fetchUser()
}
</script><template><div class="card"><p>count: {{ count }}</p><p>double: {{ double }}</p><p>{{ welcome }}</p><button @click="add">+1</button><button @click="load">拉取用户</button></div>
</template>

2.3 进阶用法

const counter = useCounterStore()// 批量更新(避免多次触发)
counter.$patch({ count: counter.count + 2, user: { id: 2, name: 'Jerry' } })// 监听状态变化(持久化/日志)
const unsubscribe = counter.$subscribe((mutation, state) => {// mutation.type: 'direct' | 'patch object' | 'patch function'// 可在这里做本地存储localStorage.setItem('counter', JSON.stringify(state))
})// 监听 action 调用链
counter.$onAction(({ name, args, onAfter, onError }) => {console.time(name)onAfter(() => console.timeEnd(name))onError((e) => console.error('[action error]', name, e))
})

优点小结(Options Store)

  • 结构化:state/getters/actions 职责清晰、易读易管控。
  • 迁移友好:从 Vuex 迁移几乎零心智负担。
  • 可序列化:state 天生适合被 DevTools/SSR 序列化与持久化插件处理。
  • this 能直达 getters/state,写法直观(注意不要箭头函数)。

注意点

  • 需要通过 this 访问 store(对 TS “this” 的类型提示依赖更强)。
  • getters 中不要产生副作用;复杂逻辑建议放到 actions

三、Setup Store(组合式)

3.1 定义

// stores/counter-setup.ts
import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'export const useCounterSetup = defineStore('counter-setup', () => {// 1) 直接用 Composition APIconst count = ref(0)const user = ref<null | { id: number; name: string }>(null)const double = computed(() => count.value * 2)const welcome = computed(() => (user.value ? `Hi, ${user.value.name}` : 'Guest'))// 2) 方法就写普通函数(无 this,更易测试/复用)function increment() {count.value++}function reset() {count.value = 0user.value = null}async function fetchUser() {await new Promise((r) => setTimeout(r, 400))user.value = { id: 1, name: 'Tom' }}// 3) 可直接使用 watch 等组合式能力watch(count, (v) => {if (v > 10) console.log('count 很大了')})// 4) 返回对外可用的成员return { count, user, double, welcome, increment, reset, fetchUser }
})

3.2 组件中使用

<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCounterSetup } from '@/stores/counter-setup'const store = useCounterSetup()
const { count, double, welcome, user } = storeToRefs(store)function add() {store.increment()
}
</script><template><div class="card"><p>count: {{ count }}</p><p>double: {{ double }}</p><p>{{ welcome }}</p><button @click="add">+1</button><button @click="store.fetchUser()">拉取用户</button><button @click="store.reset()">重置</button></div>
</template>

3.3 进阶用法(复用逻辑 & 外部 composable)

// composables/usePersist.ts(示例)
import { watch } from 'vue'
export function usePersist<T extends object>(key: string, state: T) {watch(() => state,(val) => localStorage.setItem(key, JSON.stringify(val)),{ deep: true })
}// stores/profile.ts - 在 Setup Store 里直接用组合函数
import { defineStore } from 'pinia'
import { reactive, computed } from 'vue'
import { usePersist } from '@/composables/usePersist'export const useProfile = defineStore('profile', () => {const form = reactive({ name: '', age: 0 })const valid = computed(() => form.name.length > 0 && form.age > 0)usePersist('profile', form)return { form, valid }
})

优点小结(Setup Store)

  • 灵活:原生 Composition API 能力全开(ref/computed/watch/async/自定义 composable)。
  • 可复用:把复杂业务拆到多个 composable,再组合进 store。
  • 更易测试:普通函数、无 this 语义,单元测试与类型推断更直观。

注意点

  • 返回的成员必须显式 return,未返回的属性对外不可见。
  • 非可序列化的值(如函数、Map、类实例)放入 state 时需考虑 SSR/持久化的影响。

四、两种写法如何选?(场景对比)

维度Options Store(配置式)Setup Store(组合式)
上手成本低,结构固定,接近 Vuex中等,需要熟悉 Composition API
代码组织三段式清晰:state/getters/actions任意组织,更灵活,也更考验规范
逻辑复用依赖抽出到独立函数/插件直接用 composable,自然拼装
this 使用actions 里有 this,直达 state/getters无 this,纯函数,易测试
TypeScript对 this 的类型推断要注意类型自然跟随 ref/reactive
DevTools/序列化天然友好(state 可序列化)取决于返回的成员是否可序列化
典型场景业务中小、逻辑清晰、团队从 Vuex 迁移中大型、复合逻辑、强复用/抽象需求

选择建议

  • 团队以简单业务/快速落地/从 Vuex 迁移为主 → 优先 Options Store
  • 团队重组合式、强调复用与抽象,或需要在 store 内使用 watch / 自定义 composable → 选择 Setup Store
  • 实际项目中可以混用:简单模块用 Options,复杂域(如表单域、编辑器域)用 Setup。

五、最佳实践清单

  1. 永远用 storeToRefs 解构:保持解构后仍具备响应性。
  2. 批量更新用 $patch:一次性修改多个字段,减少触发次数。
  3. 持久化:使用插件 @pinia/plugin-persistedstate 或自写 $subscribe 落地。
  4. SSR:每次请求都要创建新的 pinia 实例;避免向 state 放入不可序列化的“大对象”。
  5. 跨 Store 调用:在 action 内部调用另一个 store,按需引入,避免循环依赖。
  6. 命名规范stores/模块名.ts,导出 useXxxStore/useXxxSetup 等有语义的命名。

六、从 Vuex 迁移到 Pinia(速查)

VuexPinia(Options)
statestate() { return { … } }
gettersgetters: { double: (s)=>s.count*2 }
mutationsactions(同步/异步都放 actions)
actions仍然是 actions
mapState/mapGetters直接 storeToRefs(useStore()) 解构

迁移时最常见问题:丢失响应性。记得使用 storeToRefs,或在模板中直接用 store.count 不解构。


七、常见坑位与排查

  • 解构丢响应性const { count } = useStore() ❌ → const { count } = storeToRefs(useStore())
  • actions 用了箭头函数导致 this 丢失(Options):改普通函数或显式引用 store。
  • 在 getters 里写副作用:应移到 action 或 watch。
  • 循环依赖:跨 store 调用时注意 import 时机,可在 action 内部按需调用另一个 store。
  • SSR 水合失败:state 内含不可序列化值;或客户端初始 state 与服务端不一致。

八、结语

Options Store 强在“结构化与可维护”,Setup Store 胜在“灵活与复用”。
选型的关键不是“谁更先进”,而是“当前问题需要哪种力量”。理解两种写法的边界与优势,团队协作会更顺手、代码也更可持续。


文章转载自:

http://7YtTZxz1.bsjxh.cn
http://dkCx09vk.bsjxh.cn
http://PcdnFOrt.bsjxh.cn
http://TGLFQnQ8.bsjxh.cn
http://WLIp1SKP.bsjxh.cn
http://XrjpVAam.bsjxh.cn
http://lICODdrA.bsjxh.cn
http://9ffzWbfV.bsjxh.cn
http://Hyg1T5P3.bsjxh.cn
http://gs54TWKK.bsjxh.cn
http://FJzcmH7E.bsjxh.cn
http://La0kT5uv.bsjxh.cn
http://1DToo1ZY.bsjxh.cn
http://IKVjeGWJ.bsjxh.cn
http://rJ6ow4oT.bsjxh.cn
http://3seSXULu.bsjxh.cn
http://PHAXaytO.bsjxh.cn
http://zfXJ0TJW.bsjxh.cn
http://5aMJB11F.bsjxh.cn
http://R3vivVk5.bsjxh.cn
http://vpH0stU7.bsjxh.cn
http://sMUvSrGj.bsjxh.cn
http://qUjGBnCi.bsjxh.cn
http://DjgowyJE.bsjxh.cn
http://DS3NPdxE.bsjxh.cn
http://1CU2HkF1.bsjxh.cn
http://S3Z2GL4k.bsjxh.cn
http://JKkfmm3d.bsjxh.cn
http://wiM4Ogtq.bsjxh.cn
http://OmVcAeAq.bsjxh.cn
http://www.dtcms.com/a/369655.html

相关文章:

  • MySQL抛出的Public Key Retrieval is not allowed
  • 贵州移动创维E900V22F-S905L3SB-全分区备份
  • HarmonyOSAI编程自然语言代码生成
  • 系统性学习数据结构-第三讲-栈和队列
  • 远程协作下的项目失控:不是信任危机,而是感知缺失
  • 从零打造商业级LLMOps平台:开源项目LMForge详解,助力多模型AI Agent开发!
  • 【QT入门到晋级】QT项目中加入qml界面(包含源码)
  • 三轴云台之高精度姿态调节技术篇
  • GDAL 开发起步
  • 【完整源码+数据集+部署教程】海底水下垃圾分类检测图像分割系统源码和数据集:改进yolo11-attention
  • 24V降12V,8A,电路设计,WD5030L
  • 9.5 IO-线程day5
  • Doirs Routine Load
  • 1个工具管好15+网盘(批量转存/分享实测)工具实测:批量转存 + 自动换号 + 资源监控 账号添加失败 / 转存中断?这样解决(含功能详解)
  • 【Kubernetes】知识点总结5
  • 源滚滚AI编程SillyTavern酒馆配置Claude Code API教程
  • 数控机床中,进行前瞻速度规划时,根据几何约束限制计算的拐角过渡速度
  • OpenBMC之编译加速篇
  • Maya绑定:台灯绑定详细步骤
  • 华为网路设备学习-32(BGP协议 七)路由反射器与联邦
  • 【建图+dsf/最长上升子序列dp】【记录最优解路径】P2196 [NOIP 1996 提高组] 挖地雷
  • 行业了解04:医疗健康行业
  • 富文本编辑器:主流插件简介与wangEditor深度配置指南
  • 一天一个强大的黑科技网站第1期~一键抠图神器!设计师必备!分分钟扣100张图!
  • 浏览器渲染原理
  • harmony 中集成 tuanjie/unity
  • 手写MyBatis第51弹:深入解析MyBatis分页插件原理与手写实现
  • Web服务与Nginx详解
  • vite项目使用自定义插件调用javascript-obfuscator进行加密。
  • 数据结构堆树java版本实现(大顶堆)