关于 component-refactoring
用于重构 Dify 前端高复杂度 React 组件的技能,结合复杂度分析、Dify 特定拆分模式和分步验证流程。
主要功能
- 01分析组件复杂度及指标
- 02生成重构提示词
- 03提供 Hook 和子组件拆分指导
- 04遵循 Dify 特定重构约定
- 05分步运行 lint、类型检查和测试
适用场景
- 01拆分复杂的 Dify React 组件
- 02判断是否应提取 Hook 或界面区块
- 03通过指标和测试验证重构结果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charlie85270/ dorothy --skill component-refactoring安装后在 Claude Code 中输入/component-refactoring确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/complexity-patterns.mdcomponent-splitting.mdhook-extraction.md详细介绍
Dify 组件重构技能
按照以下模式和工作流,重构 Dify 前端代码库中复杂度较高的 React 组件。
复杂度阈值:复杂度 > 50 的组件(通过
pnpm analyze-component测量)应在测试前进行重构。
快速参考
命令(在 web/ 中运行)
使用相对于 web/ 的路径(例如 app/components/...)。
重构提示词使用 refactor-component,测试提示词和指标使用 analyze-component。
cd web
# Generate refactoring prompt
pnpm refactor-component <path>
# Output refactoring analysis as JSON
pnpm refactor-component <path> --json
# Generate testing prompt (after refactoring)
pnpm analyze-component <path>
# Output testing analysis as JSON
pnpm analyze-component <path> --json
复杂度分析
# Analyze component complexity
pnpm analyze-component <path> --json
# Key metrics to check:
# - complexity: normalized score 0-100 (target < 50)
# - maxComplexity: highest single function complexity
# - lineCount: total lines (target < 300)
复杂度评分解读
| 分数 | 级别 | 操作 |
|---|---|---|
| 0-25 | 🟢 简单 | 可以开始测试 |
| 26-50 | 🟡 中等 | 考虑进行小幅重构 |
| 51-75 | 🟠 复杂 | 测试前先重构 |
| 76-100 | 🔴 非常复杂 | 必须重构 |
核心重构模式
模式 1:提取自定义 Hook
适用情况:组件具有复杂的状态管理、多个 useState/useEffect,或业务逻辑与 UI 混杂。
Dify 约定:将 Hook 放在 hooks/ 子目录中,或与组件放在同一目录下,命名为 use-<feature>.ts。
// ❌ Before: Complex state logic in component
const Configuration: FC = () => {
const [modelConfig, setModelConfig] = useState<ModelConfig>(...)
const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>(...)
const [completionParams, setCompletionParams] = useState<FormValue>({})
// 50+ lines of state management logic...
return <div>...</div>
}
// ✅ After: Extract to custom hook
// hooks/use-model-config.ts
export const useModelConfig = (appId: string) => {
const [modelConfig, setModelConfig] = useState<ModelConfig>(...)
const [completionParams, setCompletionParams] = useState<FormValue>({})
// Related state management logic here
return { modelConfig, setModelConfig, completionParams, setCompletionParams }
}
// Component becomes cleaner
const Configuration: FC = () => {
const { modelConfig, setModelConfig } = useModelConfig(appId)
return <div>...</div>
}
Dify 示例:
web/app/components/app/configuration/hooks/use-advanced-prompt-config.tsweb/app/components/app/configuration/debug/hooks.tsxweb/app/components/workflow/hooks/use-workflow.ts
模式 2:提取子组件
适用情况:单个组件包含多个 UI 区块、条件渲染代码块,或重复模式。
Dify 约定:将子组件放在子目录中,或作为独立文件放在同一目录下。
// ❌ Before: Monolithic JSX with multiple sections
const AppInfo = () => {
return (
<div>
{/* 100 lines of header UI */}
{/* 100 lines of operations UI */}
{/* 100 lines of modals */}
</div>
)
}
// ✅ After: Split into focused components
// app-info/
// ├── index.tsx (orchestration only)
// ├── app-header.tsx (header UI)
// ├── app-operations.tsx (operations UI)
// └── app-modals.tsx (modal management)
const AppInfo = () => {
const { showModal, setShowModal } = useAppInfoModals()
return (
<div>
<AppHeader appDetail={appDetail} />
<AppOperations onAction={handleAction} />
<AppModals show={showModal} onClose={() => setShowModal(null)} />
</div>
)
}
Dify 示例:
web/app/components/app/configuration/目录结构web/app/components/workflow/nodes/按节点组织的结构
模式 3:简化条件逻辑
适用情况:嵌套层级过深(> 3 层)、三元表达式复杂,或存在多个 if/else 链。
// ❌ Before: Deeply nested conditionals
const Template = useMemo(() => {
if (appDetail?.mode === AppModeEnum.CHAT) {
switch (locale) {
case LanguagesSupported[1]:
return <TemplateChatZh />
case LanguagesSupported[7]:
return <TemplateChatJa />
default:
return <TemplateChatEn />
}
}
if (appDetail?.mode === AppModeEnum.ADVANCED_CHAT) {
// Another 15 lines...
}
// More conditions...
}, [appDetail, locale])
// ✅ After: Use lookup tables + early returns
const TEMPLATE_MAP = {
[AppModeEnum.CHAT]: {
[LanguagesSupported[1]]: TemplateChatZh,
[LanguagesSupported[7]]: TemplateChatJa,
default: TemplateChatEn,
},
[AppModeEnum.ADVANCED_CHAT]: {
[LanguagesSupported[1]]: TemplateAdvancedChatZh,
// ...
},
}
const Template = useMemo(() => {
const modeTemplates = TEMPLATE_MAP[appDetail?.mode]
if (!modeTemplates) return null
const TemplateComponent = modeTemplates[locale] || modeTemplates.default
return <TemplateComponent appDetail={appDetail} />
}, [appDetail, locale])
模式 4:提取 API/数据逻辑
适用情况:组件直接处理 API 调用、数据转换或复杂的异步操作。
Dify 约定:使用 web/service/use-*.ts 中的 @tanstack/react-query Hook,或创建自定义数据 Hook。
// ❌ Before: API logic in component
const MCPServiceCard = () => {
const [basicAppConfig, setBasicAppConfig] = useState({})
useEffect(() => {
if (isBasicApp && appId) {
(async () => {
const res = await fetchAppDetail({ url: '/apps', id: appId })
setBasicAppConfig(res?.model_config || {})
})()
}
}, [appId, isBasicApp])
// More API-related logic...
}
// ✅ After: Extract to data hook using React Query
// use-app-config.ts
import { useQuery } from '@tanstack/react-query'
import { get } from '@/service/base'
const NAME_SPACE = 'appConfig'
export const useAppConfig = (appId: string, isBasicApp: boolean) => {
return useQuery({
enabled: isBasicApp && !!appId,
queryKey: [NAME_SPACE, 'detail', appId],
queryFn: () => get<AppDetailResponse>(`/apps/${appId}`),
select: data => data?.model_config || {},
})
}
// Component becomes cleaner
const MCPServiceCard = () => {
const { data: config, isLoading } = useAppConfig(appId, isBasicApp)
// UI only
}
Dify 中的 React Query 最佳实践:
- 定义
NAME_SPACE,以便组织查询键 - 使用
enabled选项进行条件获取 - 使用
select转换数据 - 导出失效 Hook:
useInvalidXxx
Dify 示例:
web/service/use-workflow.tsweb/service/use-common.tsweb/service/knowledge/use-dataset.tsweb/service/knowledge/use-document.ts
模式 5:提取模态框/对话框管理逻辑
适用情况:组件管理多个模态框,且打开/关闭状态较为复杂。
Dify 约定:应将模态框及其状态管理提取出来。
// ❌ Before: Multiple modal states in component
const AppInfo = () => {
const [showEditModal, setShowEditModal] = useState(false)
const [showDuplicateModal, setShowDuplicateModal] = useState(false)
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [showSwitchModal, setShowSwitchModal] = useState(false)
const [showImportDSLModal, setShowImportDSLModal] = useState(false)
// 5+ more modal states...
}
// ✅ After: Extract to modal management hook
type ModalType = 'edit' | 'duplicate' | 'delete' | 'switch' | 'import' | null
const useAppInfoModals = () => {
const [activeModal, setActiveModal] = useState<ModalType>(null)
const openModal = useCallback((type: ModalType) => setActiveModal(type), [])
const closeModal = useCallback(() => setActiveModal(null), [])
return {
activeModal,
openModal,
closeModal,
isOpen: (type: ModalType) => activeModal === type,
}
}
模式 6:提取表单逻辑
适用情况:表单验证、提交处理或字段转换较为复杂。
Dify 约定:使用 web/app/components/base/form/ 中的 @tanstack/react-form 模式。
// ✅ Use existing form infrastructure
import { useAppForm } from '@/app/components/base/form'
const ConfigForm = () => {
const form = useAppForm({
defaultValues: { name: '', description: '' },
onSubmit: handleSubmit,
})
return <form.Provider>...</form.Provider>
}
Dify 专属重构指南
1. 提取 Context Provider
适用情况:组件提供包含多个状态的复杂上下文值。
// ❌ Before: Large context value object
const value = {
appId, isAPIKeySet, isTrailFinished, mode, modelModeType,
promptMode, isAdvancedMode, isAgent, isOpenAI, isFunctionCall,
// 50+ more properties...
}
return <ConfigContext.Provider value={value}>...</ConfigContext.Provider>
// ✅ After: Split into domain-specific contexts
<ModelConfigProvider value={modelConfigValue}>
<DatasetConfigProvider value={datasetConfigValue}>
<UIConfigProvider value={uiConfigValue}>
{children}
</UIConfigProvider>
</DatasetConfigProvider>
</ModelConfigProvider>
Dify 参考:web/context/ 目录结构
2. 工作流节点组件
适用情况:重构工作流节点组件(web/app/components/workflow/nodes/)。
约定:
- 将节点逻辑保留在
use-interactions.ts中 - 将面板 UI 提取到独立文件中
- 使用
_base组件处理通用模式
nodes/<node-type>/
├── index.tsx # Node registration
├── node.tsx # Node visual component
├── panel.tsx # Configuration panel
├── use-interactions.ts # Node-specific hooks
└── types.ts # Type definitions
3. 配置组件
适用情况:重构应用配置组件。
约定:
- 将配置区块拆分到子目录中
- 使用
web/app/components/app/configuration/中的现有模式 - 将功能开关放在专用组件中
4. 工具/插件组件
适用情况:重构与工具相关的组件(web/app/components/tools/)。
约定:
- 遵循现有的模态框模式
- 使用
web/service/use-tools.ts中的服务 Hook - 将特定于提供方的逻辑隔离开来
重构工作流
步骤 1:生成重构提示词
pnpm refactor-component <path>
此命令将:
- 分析组件复杂度和功能
- 确定所需的具体重构操作
- 为 AI 助手生成提示词(在 macOS 上自动复制到剪贴板)
- 根据检测到的模式提供详细要求
步骤 2:分析详情
pnpm analyze-component <path> --json
确定:
- 总复杂度评分
- 最大函数复杂度
- 行数
- 检测到的功能(状态、副作用、API 等)
步骤 3:规划
根据检测到的功能制定重构计划:
| 检测到的功能 | 重构操作 |
|---|---|
hasState: true + hasEffects: true | 提取自定义 Hook |
hasAPI: true | 提取数据/服务 Hook |
hasEvents: true(数量较多) | 提取事件处理程序 |
lineCount > 300 | 拆分为子组件 |
maxComplexity > 50 | 简化条件逻辑 |
步骤 4:逐步执行
- 每次只提取一个部分
- 每次提取后运行 lint、类型检查和测试
- 在进行下一步之前验证功能
For each extraction:
┌────────────────────────────────────────┐
│ 1. Extract code │
│ 2. Run: pnpm lint:fix │
│ 3. Run: pnpm type-check:tsgo │
│ 4. Run: pnpm test │
│ 5. Test functionality manually │
│ 6. PASS? → Next extraction │
│ FAIL? → Fix before continuing │
└────────────────────────────────────────┘
步骤 5:验证
重构后:
# Re-run refactor command to verify improvements
pnpm refactor-component <path>
# If complexity < 25 and lines < 200, you'll see:
# ✅ COMPONENT IS WELL-STRUCTURED
# For detailed metrics:
pnpm analyze-component <path> --json
# Target metrics:
# - complexity < 50
# - lineCount < 300
# - maxComplexity < 30
应避免的常见错误
❌ 过度设计
// ❌ Too many tiny hooks
const useButtonText = () => useState('Click')
const useButtonDisabled = () => useState(false)
const useButtonLoading = () => useState(false)
// ✅ Cohesive hook with related state
const useButtonState = () => {
const [text, setText] = useState('Click')
const [disabled, setDisabled] = useState(false)
const [loading, setLoading] = useState(false)
return { text, setText, disabled, setDisabled, loading, setLoading }
}
❌ 破坏现有模式
- 遵循现有目录结构
- 保持命名约定
- 保留导出模式以确保兼容性
❌ 过早抽象
- 只有在能明确降低复杂度时才进行提取
- 不要为只使用一次的代码创建抽象
- 将重构后的代码保留在同一领域
参考资料
Dify 代码库示例
- Hook 提取:
web/app/components/app/configuration/hooks/ - 组件拆分:
web/app/components/app/configuration/ - 服务 Hook:
web/service/use-*.ts - 工作流模式:
web/app/components/workflow/hooks/ - 表单模式:
web/app/components/base/form/
相关技能
frontend-testing- 用于测试重构后的组件web/testing/testing.md- 测试规范