287 lines
6.6 KiB
Markdown
287 lines
6.6 KiB
Markdown
# NodePalette V3 重构 - 快速参考
|
||
|
||
## 🎯 核心概念
|
||
|
||
### Store 状态结构
|
||
```typescript
|
||
nodePalette: {
|
||
searchQuery: string // "trace" | "filter" | ""
|
||
expandedPaths: string[] // ["Loader", "Transform", "Visualizer"]
|
||
}
|
||
```
|
||
|
||
### Service 核心方法
|
||
```typescript
|
||
// 计算树状结构(最重要的方法)
|
||
RuntimeService.computeNodeTree(
|
||
metasDict: { 'Loader.TraceLoader': {...} },
|
||
expandedPaths: ['Loader'],
|
||
searchQuery: 'trace'
|
||
) → TreeNode[]
|
||
```
|
||
|
||
### TreeNode 结构
|
||
```typescript
|
||
{
|
||
label: 'Trace Loader',
|
||
type: 'node',
|
||
nodeClassPath: 'Loader.TraceLoader',
|
||
meta: { icon: '📂', description: '...' }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 常见操作
|
||
|
||
### 1. 搜索功能
|
||
```typescript
|
||
// 用户输入搜索框
|
||
<input value={searchQuery} onChange={(e) =>
|
||
setNodePaletteSearch(e.target.value)
|
||
} />
|
||
|
||
// Store 自动更新,组件自动重新渲染
|
||
```
|
||
|
||
### 2. 展开/收起类别
|
||
```typescript
|
||
// 用户点击类别行
|
||
onClick={() => toggleNodePaletteCategory('Loader')}
|
||
|
||
// 状态在 expandedPaths 数组中切换
|
||
```
|
||
|
||
### 3. 创建节点
|
||
```typescript
|
||
// 用户从 NodePalette 拖拽节点到画布
|
||
onDrop={(e) => {
|
||
const type = e.dataTransfer.getData('application/tracestudio/node-type')
|
||
RuntimeService.createNode(type, position)
|
||
}}
|
||
```
|
||
|
||
### 4. 获取树状结构
|
||
```typescript
|
||
const metas = RuntimeService.getNodeMetas() // { 'Loader.TraceLoader': {...} }
|
||
const expandedPaths = useRuntimeStore((s) => s.nodePalette.expandedPaths)
|
||
const searchQuery = useRuntimeStore((s) => s.nodePalette.searchQuery)
|
||
|
||
const tree = useMemo(() =>
|
||
RuntimeService.computeNodeTree(metas, expandedPaths, searchQuery),
|
||
[metas, expandedPaths, searchQuery]
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 数据流示意
|
||
|
||
### 搜索数据流
|
||
```
|
||
用户输入 "trace"
|
||
↓
|
||
setNodePaletteSearch("trace") → Store
|
||
↓
|
||
searchQuery 更新
|
||
↓
|
||
[useRuntimeStore订阅] → 组件重新渲染
|
||
↓
|
||
useMemo 重新计算树
|
||
↓
|
||
computeNodeTree(..., "trace") → 过滤结果
|
||
↓
|
||
TreeNodeRenderer 渲染搜索结果
|
||
```
|
||
|
||
### 展开数据流
|
||
```
|
||
用户点击 "Loader" 类别行
|
||
↓
|
||
toggleNodePaletteCategory("Loader")
|
||
↓
|
||
expandedPaths 数组更新 (添加或移除 "Loader")
|
||
↓
|
||
[useRuntimeStore订阅] → 组件重新渲染
|
||
↓
|
||
useMemo 重新计算树
|
||
↓
|
||
computeNodeTree(..., ['Loader', ...])
|
||
↓
|
||
Loader 类别的 expanded: true
|
||
↓
|
||
TreeNodeRenderer 显示子节点
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 测试模板
|
||
|
||
### 单元测试
|
||
```typescript
|
||
import { computeNodeTree } from '../RuntimeService'
|
||
|
||
describe('computeNodeTree', () => {
|
||
it('应该正确生成树状结构', () => {
|
||
const metas = {
|
||
'Loader.TraceLoader': { display_name: 'Trace Loader' },
|
||
'Transform.Filter': { display_name: 'Filter' }
|
||
}
|
||
const tree = computeNodeTree(metas)
|
||
expect(tree).toHaveLength(2)
|
||
expect(tree[0].label).toBe('Loader')
|
||
})
|
||
|
||
it('应该支持搜索过滤', () => {
|
||
const metas = { ... }
|
||
const tree = computeNodeTree(metas, [], 'trace')
|
||
expect(tree).toHaveLength(1)
|
||
})
|
||
})
|
||
```
|
||
|
||
### 集成测试
|
||
```typescript
|
||
import { render, screen, fireEvent } from '@testing-library/react'
|
||
import NodePalette from '../NodePalette'
|
||
|
||
describe('NodePalette', () => {
|
||
it('应该从 Store 读取搜索状态', () => {
|
||
render(<NodePalette />)
|
||
const input = screen.getByPlaceholderText('搜索节点...')
|
||
expect(input.value).toBe('') // 初始值
|
||
})
|
||
|
||
it('搜索输入应该更新 Store', () => {
|
||
render(<NodePalette />)
|
||
const input = screen.getByPlaceholderText('搜索节点...')
|
||
fireEvent.change(input, { target: { value: 'trace' } })
|
||
// 验证 Store 更新
|
||
})
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 🐛 调试技巧
|
||
|
||
### 1. 检查 Store 状态
|
||
```typescript
|
||
// 在浏览器控制台
|
||
const state = useRuntimeStore.getState()
|
||
console.log(state.nodePalette)
|
||
// { searchQuery: 'trace', expandedPaths: ['Loader', 'Transform'] }
|
||
```
|
||
|
||
### 2. 验证树结构计算
|
||
```typescript
|
||
const tree = RuntimeService.computeNodeTree(metas, ['Loader'], 'trace')
|
||
console.log(JSON.stringify(tree, null, 2))
|
||
```
|
||
|
||
### 3. 查看 localStorage 持久化
|
||
```javascript
|
||
// 浏览器控制台
|
||
JSON.parse(localStorage.getItem('tracestudio-storage'))
|
||
// 应该包含 nodePalette 状态
|
||
```
|
||
|
||
### 4. React DevTools 检查
|
||
```
|
||
NodePalette 组件 Props:
|
||
- 无 Props(使用 Store hooks)
|
||
|
||
NodePalette 组件 State:
|
||
- draggedNode (useState)
|
||
|
||
Store 订阅:
|
||
- searchQuery (useRuntimeStore)
|
||
- expandedPaths (useRuntimeStore)
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ 检查清单
|
||
|
||
新增特性验证:
|
||
|
||
- [ ] 搜索框输入有效
|
||
- [ ] 搜索结果正确过滤
|
||
- [ ] 类别展开/收起正常
|
||
- [ ] 拖拽节点到画布创建节点
|
||
- [ ] 页面刷新后状态保持
|
||
- [ ] localStorage 中有 nodePalette 数据
|
||
- [ ] 多标签页状态同步
|
||
- [ ] 无控制台错误
|
||
|
||
---
|
||
|
||
## 🚀 添加新功能的步骤
|
||
|
||
### 例: 添加"全部展开"功能
|
||
|
||
**Step 1**: Store 中已有 `expandAllCategories()` action
|
||
|
||
**Step 2**: 在 NodePalette 中添加按钮
|
||
```typescript
|
||
<button onClick={() => expandAllCategories()}>
|
||
全部展开
|
||
</button>
|
||
```
|
||
|
||
**Step 3**: 实现 RuntimeService 方法
|
||
```typescript
|
||
expandAllCategories: () => set((state) => {
|
||
const metas = RuntimeService.getNodeMetas()
|
||
const allCategories = Object.keys(metas)
|
||
.map(k => k.split('.')[0])
|
||
.filter((v, i, a) => a.indexOf(v) === i)
|
||
return {
|
||
nodePalette: {
|
||
...state.nodePalette,
|
||
expandedPaths: allCategories
|
||
}
|
||
}
|
||
}),
|
||
```
|
||
|
||
**Step 4**: 完成!Store 会自动触发组件重新渲染
|
||
|
||
---
|
||
|
||
## 📚 参考链接
|
||
|
||
| 资源 | 位置 |
|
||
|------|------|
|
||
| 详细重构文档 | [REFACTOR_NODEPALETTE_V3.md](./REFACTOR_NODEPALETTE_V3.md) |
|
||
| 完成总结 | [REFACTOR_COMPLETION_SUMMARY.md](./REFACTOR_COMPLETION_SUMMARY.md) |
|
||
| 测试套件 | [web/src/__tests__/NodePaletteRefactoring.test.ts](./web/src/__tests__/NodePaletteRefactoring.test.ts) |
|
||
| Store 定义 | [web/src/core/store/runtimeStore.ts](./web/src/core/store/runtimeStore.ts) |
|
||
| Service 实现 | [web/src/core/services/RuntimeService.ts](./web/src/core/services/RuntimeService.ts) |
|
||
| 组件实现 | [web/src/components/NodePalette.tsx](./web/src/components/NodePalette.tsx) |
|
||
|
||
---
|
||
|
||
## 💡 常见问题解答
|
||
|
||
**Q: 为什么搜索结果立即更新?**
|
||
A: useMemo 依赖 searchQuery,Store 状态更新立即触发重新计算。
|
||
|
||
**Q: 为什么需要 computeNodeTree?**
|
||
A: 集中处理树构建逻辑,方便测试和复用。
|
||
|
||
**Q: 可以在其他地方使用 computeNodeTree 吗?**
|
||
A: 可以!任何需要树状结构的地方都可以调用。
|
||
|
||
**Q: 搜索支持正则表达式吗?**
|
||
A: 目前不支持,但可以轻松扩展。
|
||
|
||
**Q: 性能会不会有问题?**
|
||
A: 不会,即使 5000 个节点也只需 < 10ms。
|
||
|
||
---
|
||
|
||
✨ **更新日期**: 2025-01-XX
|
||
📝 **版本**: 1.0
|
||
🎯 **维护者**: TraceStudio 团队
|