Private
Public Access
1
0
Files
u-desk/docs/02-架构设计/架构改进方案-状态管理优化.md

486 lines
11 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.
# 架构改进方案:状态管理优化
## 问题分析
当前遇到的问题属于"响应式状态同步灾难",主要问题:
1. **状态分散**:多个 Composables 各自管理状态,难以追踪数据流
2. **响应式失效**computed/watch 在复杂场景下失效,难以调试
3. **数据传递复杂**props/computed/provide 多层传递,容易丢失
4. **缺乏状态快照**:无法回溯状态变化历史
5. **调试困难**:大量 console.log 散布在代码中,难以系统化
## 改进方案
### 1. 引入 Pinia 统一状态管理
#### 1.1 安装 Pinia
```bash
npm install pinia
```
#### 1.2 创建 Store 结构
```
stores/
├── db-cli/
│ ├── index.ts # 主 store
│ ├── connection.ts # 连接状态
│ ├── structure.ts # 表结构状态
│ ├── result.ts # 查询结果状态
│ ├── editor.ts # 编辑器状态
│ └── message.ts # 消息日志状态
└── devtools.ts # 开发工具(状态快照/回放)
```
#### 1.3 核心 Store 设计
**stores/db-cli/structure.ts** - 表结构状态管理
```typescript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export interface StructureInfo {
connectionId: number
database: string
tableName: string
dbType: 'mysql' | 'mongo' | 'redis'
nodeType: string
}
export interface StructureData {
type: string
columns?: any[]
database?: string
table?: string
// ... 其他字段
}
export const useStructureStore = defineStore('structure', () => {
// 状态定义
const loading = ref(false)
const error = ref<string | null>(null)
const data = ref<StructureData | null>(null)
const info = ref<StructureInfo | null>(null)
// 计算属性(自动响应式)
const hasData = computed(() => data.value !== null && info.value !== null)
const isReady = computed(() => !loading.value && hasData.value)
// Actions统一的数据变更入口
async function loadStructure(params: {
connectionId: number
database: string
tableName: string
dbType: 'mysql' | 'mongo' | 'redis'
nodeType: string
}) {
// 防止重复加载
if (loading.value) {
console.warn('结构正在加载中,跳过重复请求')
return
}
try {
loading.value = true
error.value = null
// 验证参数
if (params.nodeType === 'connection' || params.nodeType === 'database') {
info.value = {
...params,
tableName: ''
}
data.value = null
return
}
if (!params.tableName) {
info.value = {
...params,
tableName: ''
}
data.value = null
return
}
// 调用后端
if (!window.go?.main?.App?.GetTableStructure) {
throw new Error('Go 后端未就绪')
}
const result = await window.go.main.App.GetTableStructure(
params.connectionId,
params.database,
params.tableName
)
// 原子性更新(确保数据一致性)
data.value = result
info.value = params
// 状态变更日志(开发环境)
if (import.meta.env.DEV) {
console.log('[StructureStore] 数据加载成功', { info: params, data: result })
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '加载表结构失败'
error.value = errorMessage
data.value = null
info.value = null
if (import.meta.env.DEV) {
console.error('[StructureStore] 加载失败', err)
}
} finally {
loading.value = false
}
}
function clear() {
data.value = null
info.value = null
error.value = null
}
function reset() {
loading.value = false
error.value = null
data.value = null
info.value = null
}
return {
// 状态
loading,
error,
data,
info,
// 计算属性
hasData,
isReady,
// 方法
loadStructure,
clear,
reset
}
})
```
**stores/db-cli/index.ts** - 主 Store
```typescript
import { defineStore } from 'pinia'
import { useStructureStore } from './structure'
import { useConnectionStore } from './connection'
// ... 其他 stores
// 组合 Store提供统一访问入口
export const useDbCliStore = () => {
return {
structure: useStructureStore(),
connection: useConnectionStore(),
// ... 其他 stores
}
}
```
### 2. 组件中使用 Store
**views/db-cli/index.vue**
```typescript
<script setup lang="ts">
import { useStructureStore } from '@/stores/db-cli/structure'
// 使用 Store自动响应式无需 computed
const structureStore = useStructureStore()
// 直接使用Vue 会自动追踪
const handleTableStructure = async (data: TableStructureEvent) => {
// 单一切口,清晰的数据流
await structureStore.loadStructure({
connectionId: data.connectionId,
database: data.database,
tableName: data.tableName,
dbType: data.dbType,
nodeType: data.nodeType
})
// 切换到结构 Tab
if (resultPanelRef.value) {
resultPanelRef.value.switchToStructureTab()
}
}
</script>
<template>
<ResultPanel
:structure-loading="structureStore.loading"
:structure-error="structureStore.error"
:structure-data="structureStore.data"
:structure-info="structureStore.info"
/>
</template>
```
### 3. 状态调试工具
**stores/devtools.ts** - 开发工具
```typescript
import { watch } from 'vue'
/**
* 状态变更追踪器(仅开发环境)
*/
export function setupStateDebugger() {
if (!import.meta.env.DEV) return
// 追踪所有 store 的状态变更
const stateHistory: Array<{
timestamp: number
store: string
action: string
oldValue: any
newValue: any
}> = []
return {
log(store: string, action: string, oldValue: any, newValue: any) {
stateHistory.push({
timestamp: Date.now(),
store,
action,
oldValue: JSON.parse(JSON.stringify(oldValue)),
newValue: JSON.parse(JSON.stringify(newValue))
})
console.group(`[${store}] ${action}`)
console.log('旧值:', oldValue)
console.log('新值:', newValue)
console.log('历史记录:', stateHistory.slice(-10))
console.groupEnd()
},
getHistory() {
return stateHistory
},
clearHistory() {
stateHistory.length = 0
}
}
}
```
### 4. 类型安全增强
**types/db-cli.ts**
```typescript
// 统一类型定义
export type DbType = 'mysql' | 'mongo' | 'redis'
export type NodeType = 'connection' | 'database' | 'table' | 'collection' | 'key'
export interface ConnectionInfo {
id: number
name: string
type: DbType
host: string
port: number
database?: string
}
export interface StructureInfo {
connectionId: number
database: string
tableName: string
dbType: DbType
nodeType: NodeType
}
// 严格类型检查
export function assertStructureInfo(info: unknown): asserts info is StructureInfo {
if (!info || typeof info !== 'object') {
throw new Error('Invalid StructureInfo')
}
// ... 类型检查逻辑
}
```
### 5. 状态持久化策略
```typescript
// stores/db-cli/structure.ts
import { defineStore } from 'pinia'
import { useStorage } from '@vueuse/core'
export const useStructureStore = defineStore('structure', () => {
// 使用 localStorage 持久化(可选)
const lastStructureInfo = useStorage<StructureInfo | null>(
'db-cli-last-structure-info',
null
)
// 恢复上次查看的结构
function restoreLastStructure() {
if (lastStructureInfo.value) {
loadStructure(lastStructureInfo.value)
}
}
// 在 loadStructure 中保存
async function loadStructure(params: StructureInfo) {
// ... 加载逻辑
info.value = params
lastStructureInfo.value = params // 自动保存到 localStorage
}
return { /* ... */ }
})
```
### 6. 错误边界和恢复机制
```typescript
// stores/db-cli/structure.ts
export const useStructureStore = defineStore('structure', () => {
const retryCount = ref(0)
const maxRetries = 3
async function loadStructure(params: StructureInfo, retry = 0) {
try {
// ... 加载逻辑
retryCount.value = 0 // 成功后重置
} catch (err) {
if (retry < maxRetries) {
console.warn(`[StructureStore] 重试加载 (${retry + 1}/${maxRetries})`)
await new Promise(resolve => setTimeout(resolve, 1000 * (retry + 1)))
return loadStructure(params, retry + 1)
}
// 超过重试次数,记录错误
error.value = `加载失败(已重试 ${maxRetries} 次): ${err}`
}
}
return { /* ... */ }
})
```
### 7. 组件级状态同步检查
```typescript
// composables/useStateSync.ts
import { watch, nextTick } from 'vue'
/**
* 状态同步检查器
* 确保 Store 状态和组件 props 保持同步
*/
export function useStateSync<T>(
storeValue: () => T,
propValue: () => T,
name: string
) {
if (!import.meta.env.DEV) return
watch(
() => storeValue(),
(storeVal) => {
nextTick(() => {
const propVal = propValue()
if (storeVal !== propVal) {
console.error(
`[StateSync] ${name} 不同步!`,
`Store: ${JSON.stringify(storeVal)}`,
`Prop: ${JSON.stringify(propVal)}`
)
}
})
},
{ deep: true }
)
}
```
### 8. 测试策略
```typescript
// stores/db-cli/structure.test.ts
import { setActivePinia, createPinia } from 'pinia'
import { useStructureStore } from './structure'
describe('StructureStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('应该正确加载结构数据', async () => {
const store = useStructureStore()
await store.loadStructure({
connectionId: 1,
database: 'test',
tableName: 'users',
dbType: 'mysql',
nodeType: 'table'
})
expect(store.loading).toBe(false)
expect(store.data).not.toBeNull()
expect(store.info).not.toBeNull()
})
it('应该在加载失败时设置错误', async () => {
// ... 测试错误处理
})
})
```
## 迁移步骤
1. **阶段一:引入 Pinia**
- 安装依赖
- 创建基础 Store 结构
- 在主应用初始化 Pinia
2. **阶段二:迁移状态**
- 先迁移 structure store当前问题所在
- 逐步迁移其他 stores
- 保持双写一段时间Composable + Store
3. **阶段三:清理代码**
- 移除旧的 Composables
- 统一使用 Store
- 添加类型定义
4. **阶段四:优化和测试**
- 添加状态调试工具
- 编写单元测试
- 性能优化
## 优势总结
1. **单一数据源**:所有状态集中在 Store避免分散
2. **自动响应式**Pinia 自动处理响应式,无需手动 computed
3. **开发工具**Pinia DevTools 可以可视化状态变化
4. **类型安全**TypeScript 支持更好
5. **易于测试**Store 可以独立测试
6. **状态持久化**:内置支持 localStorage/sessionStorage
7. **调试友好**:可以回放状态变更历史
## 注意事项
1. **不要过度使用**:简单的局部状态仍可使用 ref/reactive
2. **避免循环依赖**Store 之间不要相互依赖
3. **性能考虑**:大数据量使用 shallowRef
4. **SSR 兼容**:如需 SSR注意状态初始化
## 参考资料
- [Pinia 官方文档](https://pinia.vuejs.org/)
- [Vue 3 Composition API 最佳实践](https://vuejs.org/guide/extras/composition-api-faq.html)