pinia
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePinia State Management Skill
Pinia状态管理技能
File Organization: This skill uses split structure. Seefor advanced patterns and security examples.references/
文件组织:本技能采用拆分结构。高级模式和安全示例请查看目录。references/
1. Overview
1. 概述
This skill provides Pinia expertise for managing application state in the JARVIS AI Assistant, including system metrics, user preferences, and HUD configuration.
Risk Level: MEDIUM - Manages sensitive state, SSR considerations, potential data exposure
Primary Use Cases:
- System metrics and status tracking
- User preferences and settings
- HUD configuration state
- Command history and queue
- Real-time data synchronization
本技能为JARVIS AI助手提供Pinia状态管理能力,涵盖系统指标、用户偏好和HUD配置等场景。
风险等级:中等 - 管理敏感状态,需考虑SSR场景,存在数据泄露风险
主要使用场景:
- 系统指标与状态追踪
- 用户偏好与设置管理
- HUD配置状态维护
- 命令历史与队列管理
- 实时数据同步
2. Core Responsibilities
2. 核心职责
2.1 Core Principles
2.1 核心原则
- TDD First: Write store tests before implementation
- Performance Aware: Optimize subscriptions and computed values
- Type Safety: Define stores with full TypeScript typing
- SSR Security: Prevent state leakage between requests
- Composition API: Use setup stores for better TypeScript support
- Minimal State: Store only necessary data, derive the rest
- Action Validation: Validate inputs in actions before mutations
- Persistence Security: Never persist sensitive data to localStorage
- TDD优先:在实现前编写Store测试用例
- 性能感知:优化订阅与计算属性
- 类型安全:使用完整TypeScript类型定义Store
- SSR安全:防止请求间的状态泄露
- 组合式API:使用Setup Store以获得更好的TypeScript支持
- 最小化状态:仅存储必要数据,其余数据通过推导获取
- 动作校验:在修改状态前校验动作输入
- 持久化安全:绝不要将敏感数据持久化到localStorage
3. Technology Stack & Versions
3. 技术栈与版本
3.1 Recommended Versions
3.1 推荐版本
| Package | Version | Notes |
|---|---|---|
| pinia | ^2.1.0 | Latest stable |
| @pinia/nuxt | ^0.5.0 | Nuxt integration |
| pinia-plugin-persistedstate | ^3.0.0 | Optional persistence |
| Package | Version | 说明 |
|---|---|---|
| pinia | ^2.1.0 | 最新稳定版 |
| @pinia/nuxt | ^0.5.0 | Nuxt集成包 |
| pinia-plugin-persistedstate | ^3.0.0 | 可选持久化插件 |
3.2 Nuxt Configuration
3.2 Nuxt配置
typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
pinia: {
storesDirs: ['./stores/**']
}
})typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
pinia: {
storesDirs: ['./stores/**']
}
})3.3 Implementation Workflow (TDD)
3.3 实现流程(TDD)
Follow this workflow for every store:
Step 1: Write Failing Test First
typescript
// tests/stores/metrics.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useMetricsStore } from '~/stores/metrics'
describe('MetricsStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('should initialize with default values', () => {
const store = useMetricsStore()
expect(store.cpu).toBe(0)
expect(store.memory).toBe(0)
})
it('should clamp values within valid range', () => {
const store = useMetricsStore()
store.updateCpu(150)
expect(store.cpu).toBe(100)
store.updateCpu(-50)
expect(store.cpu).toBe(0)
})
it('should compute health status correctly', () => {
const store = useMetricsStore()
store.updateCpu(95)
store.updateMemory(90)
expect(store.healthStatus).toBe('critical')
})
})Step 2: Implement Minimum to Pass
typescript
// stores/metrics.ts
export const useMetricsStore = defineStore('metrics', () => {
const cpu = ref(0)
const memory = ref(0)
const healthStatus = computed(() => {
const avg = (cpu.value + memory.value) / 2
if (avg > 90) return 'critical'
if (avg > 70) return 'warning'
return 'healthy'
})
function updateCpu(value: number) {
cpu.value = Math.max(0, Math.min(100, value))
}
function updateMemory(value: number) {
memory.value = Math.max(0, Math.min(100, value))
}
return { cpu, memory, healthStatus, updateCpu, updateMemory }
})Step 3: Refactor Following Patterns
- Extract validation logic
- Add TypeScript interfaces
- Optimize computed dependencies
Step 4: Run Full Verification
bash
npm run test -- --filter=stores
npm run typecheck
npm run build每个Store都遵循以下流程:
步骤1:先编写失败的测试用例
typescript
// tests/stores/metrics.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useMetricsStore } from '~/stores/metrics'
describe('MetricsStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('should initialize with default values', () => {
const store = useMetricsStore()
expect(store.cpu).toBe(0)
expect(store.memory).toBe(0)
})
it('should clamp values within valid range', () => {
const store = useMetricsStore()
store.updateCpu(150)
expect(store.cpu).toBe(100)
store.updateCpu(-50)
expect(store.cpu).toBe(0)
})
it('should compute health status correctly', () => {
const store = useMetricsStore()
store.updateCpu(95)
store.updateMemory(90)
expect(store.healthStatus).toBe('critical')
})
})步骤2:实现最小代码以通过测试
typescript
// stores/metrics.ts
export const useMetricsStore = defineStore('metrics', () => {
const cpu = ref(0)
const memory = ref(0)
const healthStatus = computed(() => {
const avg = (cpu.value + memory.value) / 2
if (avg > 90) return 'critical'
if (avg > 70) return 'warning'
return 'healthy'
})
function updateCpu(value: number) {
cpu.value = Math.max(0, Math.min(100, value))
}
function updateMemory(value: number) {
memory.value = Math.max(0, Math.min(100, value))
}
return { cpu, memory, healthStatus, updateCpu, updateMemory }
})步骤3:遵循模式重构代码
- 提取校验逻辑
- 添加TypeScript接口
- 优化计算属性依赖
步骤4:运行完整验证
bash
npm run test -- --filter=stores
npm run typecheck
npm run build4. Implementation Patterns
4. 实现模式
4.1 Setup Store with TypeScript
4.1 基于TypeScript的Setup Store
typescript
// stores/jarvis.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
interface SystemMetrics {
cpu: number
memory: number
network: number
timestamp: number
}
interface JARVISState {
status: 'idle' | 'listening' | 'processing' | 'responding'
securityLevel: 'normal' | 'elevated' | 'lockdown'
}
export const useJarvisStore = defineStore('jarvis', () => {
// State
const state = ref<JARVISState>({
status: 'idle',
securityLevel: 'normal'
})
const metrics = ref<SystemMetrics>({
cpu: 0,
memory: 0,
network: 0,
timestamp: Date.now()
})
// Getters
const isActive = computed(() =>
state.value.status !== 'idle'
)
const systemHealth = computed(() => {
const avg = (metrics.value.cpu + metrics.value.memory) / 2
if (avg > 90) return 'critical'
if (avg > 70) return 'warning'
return 'healthy'
})
// Actions
function updateMetrics(newMetrics: Partial<SystemMetrics>) {
// ✅ Validate input
if (newMetrics.cpu !== undefined) {
metrics.value.cpu = Math.max(0, Math.min(100, newMetrics.cpu))
}
if (newMetrics.memory !== undefined) {
metrics.value.memory = Math.max(0, Math.min(100, newMetrics.memory))
}
if (newMetrics.network !== undefined) {
metrics.value.network = Math.max(0, newMetrics.network)
}
metrics.value.timestamp = Date.now()
}
function setStatus(newStatus: JARVISState['status']) {
state.value.status = newStatus
}
function setSecurityLevel(level: JARVISState['securityLevel']) {
state.value.securityLevel = level
// ✅ Audit security changes
console.info(`Security level changed to: ${level}`)
}
return {
state,
metrics,
isActive,
systemHealth,
updateMetrics,
setStatus,
setSecurityLevel
}
})typescript
// stores/jarvis.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
interface SystemMetrics {
cpu: number
memory: number
network: number
timestamp: number
}
interface JARVISState {
status: 'idle' | 'listening' | 'processing' | 'responding'
securityLevel: 'normal' | 'elevated' | 'lockdown'
}
export const useJarvisStore = defineStore('jarvis', () => {
// 状态
const state = ref<JARVISState>({
status: 'idle',
securityLevel: 'normal'
})
const metrics = ref<SystemMetrics>({
cpu: 0,
memory: 0,
network: 0,
timestamp: Date.now()
})
// 计算属性
const isActive = computed(() =>
state.value.status !== 'idle'
)
const systemHealth = computed(() => {
const avg = (metrics.value.cpu + metrics.value.memory) / 2
if (avg > 90) return 'critical'
if (avg > 70) return 'warning'
return 'healthy'
})
// 动作
function updateMetrics(newMetrics: Partial<SystemMetrics>) {
// ✅ 校验输入
if (newMetrics.cpu !== undefined) {
metrics.value.cpu = Math.max(0, Math.min(100, newMetrics.cpu))
}
if (newMetrics.memory !== undefined) {
metrics.value.memory = Math.max(0, Math.min(100, newMetrics.memory))
}
if (newMetrics.network !== undefined) {
metrics.value.network = Math.max(0, newMetrics.network)
}
metrics.value.timestamp = Date.now()
}
function setStatus(newStatus: JARVISState['status']) {
state.value.status = newStatus
}
function setSecurityLevel(level: JARVISState['securityLevel']) {
state.value.securityLevel = level
// ✅ 审计安全等级变更
console.info(`Security level changed to: ${level}`)
}
return {
state,
metrics,
isActive,
systemHealth,
updateMetrics,
setStatus,
setSecurityLevel
}
})4.2 User Preferences Store (with Persistence)
4.2 用户偏好Store(带持久化)
typescript
// stores/preferences.ts
export const usePreferencesStore = defineStore('preferences', () => {
const preferences = ref({
theme: 'dark' as 'dark' | 'light',
hudOpacity: 0.8,
soundEnabled: true
})
function updatePreference<K extends keyof typeof preferences.value>(
key: K, value: typeof preferences.value[K]
) {
if (key === 'hudOpacity' && (value < 0 || value > 1)) return
preferences.value[key] = value
}
return { preferences, updatePreference }
}, {
persist: {
key: 'jarvis-preferences',
paths: ['preferences.theme', 'preferences.hudOpacity']
// ❌ Never persist: tokens, passwords, API keys
}
})typescript
// stores/preferences.ts
export const usePreferencesStore = defineStore('preferences', () => {
const preferences = ref({
theme: 'dark' as 'dark' | 'light',
hudOpacity: 0.8,
soundEnabled: true
})
function updatePreference<K extends keyof typeof preferences.value>(
key: K, value: typeof preferences.value[K]
) {
if (key === 'hudOpacity' && (value < 0 || value > 1)) return
preferences.value[key] = value
}
return { preferences, updatePreference }
}, {
persist: {
key: 'jarvis-preferences',
paths: ['preferences.theme', 'preferences.hudOpacity']
// ❌ 绝不要持久化:令牌、密码、API密钥
}
})4.3 Command Queue Store
4.3 命令队列Store
typescript
// stores/commands.ts
interface Command {
id: string
action: string
status: 'pending' | 'executing' | 'completed' | 'failed'
}
export const useCommandStore = defineStore('commands', () => {
const queue = ref<Command[]>([])
const history = ref<Command[]>([])
const MAX_HISTORY = 100
const pendingCommands = computed(() =>
queue.value.filter(cmd => cmd.status === 'pending')
)
function addCommand(action: string) {
const cmd: Command = { id: crypto.randomUUID(), action, status: 'pending' }
queue.value.push(cmd)
return cmd.id
}
function completeCommand(id: string, status: 'completed' | 'failed') {
const idx = queue.value.findIndex(cmd => cmd.id === id)
if (idx !== -1) {
const [cmd] = queue.value.splice(idx, 1)
cmd.status = status
history.value = [cmd, ...history.value].slice(0, MAX_HISTORY)
}
}
return { queue, history, pendingCommands, addCommand, completeCommand }
})typescript
// stores/commands.ts
interface Command {
id: string
action: string
status: 'pending' | 'executing' | 'completed' | 'failed'
}
export const useCommandStore = defineStore('commands', () => {
const queue = ref<Command[]>([])
const history = ref<Command[]>([])
const MAX_HISTORY = 100
const pendingCommands = computed(() =>
queue.value.filter(cmd => cmd.status === 'pending')
)
function addCommand(action: string) {
const cmd: Command = { id: crypto.randomUUID(), action, status: 'pending' }
queue.value.push(cmd)
return cmd.id
}
function completeCommand(id: string, status: 'completed' | 'failed') {
const idx = queue.value.findIndex(cmd => cmd.id === id)
if (idx !== -1) {
const [cmd] = queue.value.splice(idx, 1)
cmd.status = status
history.value = [cmd, ...history.value].slice(0, MAX_HISTORY)
}
}
return { queue, history, pendingCommands, addCommand, completeCommand }
})4.4 SSR-Safe Store Usage
4.4 SSR安全的Store使用方式
vue
<script setup lang="ts">
// ✅ Safe for SSR - store initialized per-request
const jarvisStore = useJarvisStore()
// ✅ Fetch data on server
const { data } = await useFetch('/api/metrics')
// Update store with fetched data
if (data.value) {
jarvisStore.updateMetrics(data.value)
}
</script>vue
<script setup lang="ts">
// ✅ SSR安全 - 每个请求初始化一次Store
const jarvisStore = useJarvisStore()
// ✅ 在服务端获取数据
const { data } = await useFetch('/api/metrics')
// 用获取到的数据更新Store
if (data.value) {
jarvisStore.updateMetrics(data.value)
}
</script>4.5 Store Composition
4.5 Store组合
typescript
// stores/dashboard.ts
export const useDashboardStore = defineStore('dashboard', () => {
// ✅ Compose from other stores
const jarvisStore = useJarvisStore()
const commandStore = useCommandStore()
const dashboardStatus = computed(() => ({
systemHealth: jarvisStore.systemHealth,
pendingCommands: commandStore.pendingCommands.length,
isActive: jarvisStore.isActive
}))
return {
dashboardStatus
}
})typescript
// stores/dashboard.ts
export const useDashboardStore = defineStore('dashboard', () => {
// ✅ 从其他Store组合
const jarvisStore = useJarvisStore()
const commandStore = useCommandStore()
const dashboardStatus = computed(() => ({
systemHealth: jarvisStore.systemHealth,
pendingCommands: commandStore.pendingCommands.length,
isActive: jarvisStore.isActive
}))
return {
dashboardStatus
}
})5. Security Standards
5. 安全标准
5.1 OWASP Coverage
5.1 OWASP覆盖
| OWASP Category | Risk | Mitigation |
|---|---|---|
| A01 Broken Access Control | MEDIUM | Validate actions, check permissions |
| A04 Insecure Design | MEDIUM | SSR state isolation |
| A07 Auth Failures | MEDIUM | Never persist tokens |
| OWASP分类 | 风险等级 | 缓解措施 |
|---|---|---|
| A01 访问控制失效 | 中等 | 校验动作权限 |
| A04 不安全设计 | 中等 | SSR状态隔离 |
| A07 认证失败 | 中等 | 绝不持久化令牌 |
5.3 Sensitive Data Handling
5.3 敏感数据处理
typescript
// ❌ NEVER persist: tokens, API keys, passwords
// ✅ Store sensitive data in memory only (no persist option)
const authStore = defineStore('auth', () => {
const token = ref<string | null>(null)
return { token }
})typescript
// ❌ 绝不要持久化:令牌、API密钥、密码
// ✅ 敏感数据仅存储在内存中(不使用persist选项)
const authStore = defineStore('auth', () => {
const token = ref<string | null>(null)
return { token }
})5.5 Performance Patterns
5.5 性能优化模式
Pattern 1: Selective Subscriptions
模式1:选择性订阅
typescript
// BAD - Subscribes to entire store
const store = useJarvisStore()
watch(() => store.state, () => { /* ... */ }, { deep: true })
// GOOD - Subscribe to specific properties
const store = useJarvisStore()
watch(() => store.state.status, (newStatus) => {
console.log('Status changed:', newStatus)
})typescript
// 不良实践 - 订阅整个Store
const store = useJarvisStore()
watch(() => store.state, () => { /* ... */ }, { deep: true })
// 最佳实践 - 订阅特定属性
const store = useJarvisStore()
watch(() => store.state.status, (newStatus) => {
console.log('Status changed:', newStatus)
})Pattern 2: Computed Getters (Memoization)
模式2:计算属性(记忆化)
typescript
// BAD - Recalculates on every access
function getFilteredItems() {
return items.value.filter(i => i.active)
}
// GOOD - Cached until dependencies change
const filteredItems = computed(() =>
items.value.filter(i => i.active)
)typescript
// 不良实践 - 每次访问都重新计算
function getFilteredItems() {
return items.value.filter(i => i.active)
}
// 最佳实践 - 缓存结果直到依赖变更
const filteredItems = computed(() =>
items.value.filter(i => i.active)
)Pattern 3: Batch Updates
模式3:批量更新
typescript
// BAD - Multiple reactive triggers
function updateAll(data: MetricsData) {
metrics.value.cpu = data.cpu
metrics.value.memory = data.memory
metrics.value.network = data.network
}
// GOOD - Single reactive trigger
function updateAll(data: MetricsData) {
metrics.value = { ...metrics.value, ...data, timestamp: Date.now() }
}typescript
// 不良实践 - 多次触发响应式更新
function updateAll(data: MetricsData) {
metrics.value.cpu = data.cpu
metrics.value.memory = data.memory
metrics.value.network = data.network
}
// 最佳实践 - 单次触发响应式更新
function updateAll(data: MetricsData) {
metrics.value = { ...metrics.value, ...data, timestamp: Date.now() }
}Pattern 4: Lazy Store Initialization
模式4:懒加载Store初始化
typescript
// BAD - Store initializes immediately
const heavyStore = useHeavyDataStore()
// GOOD - Initialize only when needed
const heavyStore = ref<ReturnType<typeof useHeavyDataStore> | null>(null)
function loadHeavyData() {
if (!heavyStore.value) {
heavyStore.value = useHeavyDataStore()
}
return heavyStore.value
}typescript
// 不良实践 - Store立即初始化
const heavyStore = useHeavyDataStore()
// 最佳实践 - 仅在需要时初始化
const heavyStore = ref<ReturnType<typeof useHeavyDataStore> | null>(null)
function loadHeavyData() {
if (!heavyStore.value) {
heavyStore.value = useHeavyDataStore()
}
return heavyStore.value
}Pattern 5: Optimistic Updates
模式5:乐观更新
typescript
// BAD - Wait for server response
async function deleteItem(id: string) {
await api.delete(`/items/${id}`)
items.value = items.value.filter(i => i.id !== id)
}
// GOOD - Update immediately, rollback on error
async function deleteItem(id: string) {
const backup = [...items.value]
items.value = items.value.filter(i => i.id !== id)
try {
await api.delete(`/items/${id}`)
} catch (error) {
items.value = backup // Rollback
throw error
}
}typescript
// 不良实践 - 等待服务端响应后更新
async function deleteItem(id: string) {
await api.delete(`/items/${id}`)
items.value = items.value.filter(i => i.id !== id)
}
// 最佳实践 - 立即更新,错误时回滚
async function deleteItem(id: string) {
const backup = [...items.value]
items.value = items.value.filter(i => i.id !== id)
try {
await api.delete(`/items/${id}`)
} catch (error) {
items.value = backup // 回滚
throw error
}
}6. Testing & Quality
6. 测试与质量
See Section 3.3 for complete TDD workflow with vitest examples.
完整的TDD流程及vitest示例请查看3.3节。
8. Common Anti-Patterns
8. 常见反模式
Security Anti-Patterns
安全反模式
typescript
// ❌ Global state leaks between SSR users
const state = reactive({ user: null })
// ✅ Pinia isolates per-request
export const useUserStore = defineStore('user', () => {
const user = ref(null)
return { user }
})
// ❌ Never persist auth tokens (XSS risk)
persist: { paths: ['authToken'] }
// ✅ Use httpOnly cookies for authtypescript
// ❌ SSR用户间全局状态泄露
const state = reactive({ user: null })
// ✅ Pinia实现请求级状态隔离
export const useUserStore = defineStore('user', () => {
const user = ref(null)
return { user }
})
// ❌ 绝不要持久化认证令牌(存在XSS风险)
persist: { paths: ['authToken'] }
// ✅ 使用httpOnly Cookie处理认证Performance Anti-Patterns
性能反模式
See Section 5.5 for detailed performance patterns with Good/Bad examples.
带好坏示例的详细性能优化模式请查看5.5节。
13. Pre-Implementation Checklist
13. 实现前检查清单
Phase 1: Before Writing Code
阶段1:编写代码前
- Store interface designed with TypeScript types
- Test file created with failing tests
- Security requirements identified (persistence, SSR)
- Performance patterns selected for use case
- 已用TypeScript类型设计Store接口
- 已创建包含失败测试用例的测试文件
- 已识别安全需求(持久化、SSR)
- 已为场景选择合适的性能优化模式
Phase 2: During Implementation
阶段2:实现过程中
- Tests passing after each feature added
- Actions validate all inputs
- Computed values use minimal dependencies
- No sensitive data in persisted state
- SSR state properly isolated
- 新增功能后测试用例通过
- 动作已校验所有输入
- 计算属性使用最小依赖
- 持久化状态中无敏感数据
- SSR状态已正确隔离
Phase 3: Before Committing
阶段3:提交代码前
- All store tests passing:
npm run test -- --filter=stores - Type check passing:
npm run typecheck - Build succeeds:
npm run build - No global state outside Pinia
- State shape documented in types
- 所有Store测试通过:
npm run test -- --filter=stores - 类型检查通过:
npm run typecheck - 构建成功:
npm run build - Pinia外无全局状态
- 状态结构已在类型中记录
14. Summary
14. 总结
Pinia provides type-safe state management for JARVIS:
- TDD First: Write store tests before implementation
- Performance: Optimize subscriptions and computed values
- Security: Never persist sensitive data, isolate SSR state
- Type Safety: Use setup stores with full TypeScript
References: See for advanced patterns and security examples.
references/Pinia为JARVIS提供类型安全的状态管理能力:
- TDD优先:在实现前编写Store测试用例
- 性能优化:优化订阅与计算属性
- 安全保障:绝不持久化敏感数据,隔离SSR状态
- 类型安全:使用带完整TypeScript支持的Setup Store
参考资料:高级模式与安全示例请查看目录。
references/