当前位置: 首页 > 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://www.xdnf.cn/news/1472545.html

相关文章:

  • (3)Seata AT 模式的事务一致性保证机制
  • MySQL慢查询优化策略
  • 洛谷 P2392 kkksc03考前临时抱佛脚-普及-
  • 【C++题解】贪心和模拟
  • Linux设备down机,如何识别是 断电还是软件复位
  • Java笔记20240726
  • 【Day 22】94.二叉树的中序遍历 104.二叉树的最大深度 226.翻转二叉树 101.对称二叉树
  • linux上nexus安装教程
  • 从“下山”到AI引擎:全面理解梯度下降(下)
  • 学习心得分享
  • 【OJ】C++ vector类OJ题
  • 使用国内镜像源解决 Electron 安装卡在 postinstall 的问题
  • 【Python - 类库 - BeautifulSoup】(01)“BeautifulSoup“使用示例
  • ESP-idf注册双服务器配置
  • SemiSAM+:在基础模型时代重新思考半监督医学图像分割|文献速递-深度学习人工智能医疗图像
  • 笔记:现代操作系统:原理与实现(2)
  • CLIP学习
  • 【C++】Vector完全指南:动态数组高效使用
  • Transformer核心—自注意力机制
  • 大批项目经理被迫上前线,酸爽
  • 图片在vue2中引用的方式和优缺点
  • 【数字孪生核心技术】什么是倾斜摄影?
  • 遇到 Git 提示大文件无法上传确实让人头疼
  • SVT-AV1编码器中实现WPP依赖管理核心调度
  • 门控MLP(Qwen3MLP)与稀疏混合专家(Qwen3MoeSparseMoeBlock)模块解析
  • 【开题答辩全过程】以 基于JSP的宠物医院管理系统设计为例,包含答辩的问题和答案
  • LTV-1008-TP1-G 电子元器件 LiteOn光宝 发光二极管 核心解析
  • 字符串(2)
  • 一文读懂 RAG 与 KAG:原理、工程落地与开源实战
  • scrypt 密钥派生算法(RFC7914)技术解析及源码示例