Files
pulse-libs/skills/vue-composition-api-best-practices/reference/store-without-pattern.md
T
Pulse ae39e45460 feat: biblioteca inteligente libs/ + 5 novas skills (20 skills total)
NOVAS SKILLS:
- next-best-practices      v0.1.0  (CLEAN) — Next.js App Router, RSC, caching, data
- nextjs-patterns          v1.0.0  (CLEAN) — Next.js 15: Server Actions, route handlers
- vite                     v1.0.0  (CLEAN) — env vars, aliases, proxy, CJS compat
- uncle-bob                v1.0.0  (CLEAN) — Clean Code, SOLID, Clean Architecture
- clean-code-review        v1.0.0  (CLEAN) — naming, guard clauses, anti-patterns, refactoring
- vue                      v1.0.0  (CLEAN) — Vue framework
- vue-composition-api-best-practices v1.0.0 (CLEAN) — composables, Pinia, reactivity

BIBLIOTECA INTELIGENTE libs/ (10 dominios, 11 arquivos):
- typescript/ — TS safe + generics gotchas
- react/ — Next.js App Router + Vite config
- vue/ — Composition API + Pinia
- linux/ — System diagnostic cheatsheet
- database/ — PostgreSQL + MySQL patterns
- browser/ — Chromium CLI + E2E testing
- security/ — SAST audit (OWASP Top 10)
- best-practices/ — Clean Code + SOLID + Clean Architecture
- deploy/ — Docker multi-stack + OpenClaw ops
- + INDEX.md como guia de navegacao

.learnings/ — LRN-20260519-003 criado (biblioteca compartilhada)
2026-05-19 21:03:25 -03:00

165 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Store Without 模式
## 问题
Pinia 的 `useStore()` 默认依赖 Vue 组件上下文(inject/provide)。在组件外(hooks、utils、plugins、路由守卫、axios 拦截器)直接调用会抛出错误:
```
Error: "getActivePinia()" was called but there was no active Pinia.
```
## 解决方案:Store Without 模式
每个 store 模块额外导出一个 `useXxxStoreWithOut` 函数,接收全局 pinia 实例作为参数,使 store 可在任意上下文中安全访问。
### 模式定义
```typescript
// store/modules/app.ts
import { defineStore } from 'pinia'
import { store } from '@/store'
export const useAppStore = defineStore('app', {
// ... store 定义
})
// 在组件外使用时,传入全局 pinia 实例
export const useAppStoreWithOut = () => {
return useAppStore(store)
}
```
### 全局 Pinia 实例
```typescript
// store/index.ts
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
// 导出 store 供 Without 函数使用
export const store = pinia
```
## 使用规则
### 何时使用哪个
| 函数 | 使用场景 | 原因 |
|------|---------|------|
| `useAppStore()` | Vue 组件 `<script setup>` 内 | 自动从组件上下文获取 pinia |
| `useAppStoreWithOut()` | hooks、utils、plugins、路由守卫等 | 组件上下文不可用,需显式传入 pinia |
### 在 Vue 组件中(始终使用标准方式)
```typescript
// ✅ GOOD:组件内使用标准方式
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
</script>
```
```typescript
// ❌ BAD:组件内使用 WithOut 是多余的
<script setup lang="ts">
import { useAppStoreWithOut } from '@/store/modules/app'
const appStore = useAppStoreWithOut() // 可以工作但不必要
</script>
```
### 在 Hooks / Utils / Plugins 中(必须使用 WithOut
```typescript
// ✅ GOODhooks 中使用 WithOut
// hooks/web/useSideCategory.ts
import { useBusinessStoreWithOut } from '@/store/modules/business'
export function useSideCategory() {
const businessStore = useBusinessStoreWithOut()
const categories = computed(() => businessStore.getSideCategory)
return { categories }
}
```
```typescript
// ✅ GOODutils 中使用 WithOut
// utils/migration.ts
import { useBusinessStoreWithOut } from '@/store/modules/business'
export async function migrateOnlineIcons() {
const businessStore = useBusinessStoreWithOut()
// ...
}
```
```typescript
// ❌ BAD:组件外直接使用标准方式会报错
// utils/migration.ts
import { useBusinessStore } from '@/store/modules/business'
export async function migrateOnlineIcons() {
const businessStore = useBusinessStore() // Error: no active Pinia
}
```
## 命名规范
所有模块遵循统一命名规范:
| Store 模块 | 标准函数 | WithOut 函数 |
|-----------|---------|-------------|
| `app.ts` | `useAppStore` | `useAppStoreWithOut` |
| `business.ts` | `useBusinessStore` | `useBusinessStoreWithOut` |
| `dict.ts` | `useDictStore` | `useDictStoreWithOut` |
| `locale.ts` | `useLocaleStore` | `useLocaleStoreWithOut` |
**规则**`use{ModuleName}StoreWithOut` — 模块名首字母大写 + Store + WithOut(注意大小写)。
## 实现清单
每个 store 模块必须:
- [ ] 导出标准 `useXxxStore` 函数(`defineStore` 的返回值)
- [ ] 导出 `useXxxStoreWithOut` 函数,内部调用 `useXxxStore(store)`
- [ ]`@/store` 导入全局 `store` 实例
- [ ] WithOut 函数放在文件底部,紧跟标准函数之后
## 为什么不直接使用 `useXxxStore(pinia)`
理论上可以直接调用 `useAppStore(pinia)`,但 `WithOut` 函数提供了:
1. **语义明确** — 函数名直接表达"在组件外使用"的意图
2. **统一入口** — 不需要每个调用方都 import `store`,减少依赖
3. **集中管理** — 如果 pinia 实例获取方式变更,只需改 WithOut 函数
4. **可搜索** — 搜索 `WithOut` 即可找到所有组件外使用 store 的地方
## 替代方案:`storeToRefs` 注意事项
注意 `useXxxStoreWithOut()` 返回的是 store 实例,如需解构响应式属性,仍需使用 `storeToRefs`
```typescript
import { storeToRefs } from 'pinia'
import { useAppStoreWithOut } from '@/store/modules/app'
export function useAppInfo() {
const appStore = useAppStoreWithOut()
const { pure, theme } = storeToRefs(appStore) // 保持响应式
return { pure, theme }
}
```
## 总结
| 优势 | 说明 |
|------|------|
| 解决组件外访问 | 核心价值,让 store 可在任意上下文使用 |
| 命名约定清晰 | `WithOut` 后缀一目了然 |
| 减少样板代码 | 调用方无需 import store |
| 易于维护 | pinia 实例变更只需改一处 |
| 可追溯 | 搜索 `WithOut` 可定位所有组件外使用 |