component-refactoring

安装量: 2.7K
排名: #784

安装

npx skills add https://github.com/langgenius/dify --skill component-refactoring

Dify Component Refactoring Skill

Refactor high-complexity React components in the Dify frontend codebase with the patterns and workflow below.

Complexity Threshold: Components with complexity > 50 (measured by pnpm analyze-component) should be refactored before testing.

Quick Reference Commands (run from web/)

Use paths relative to web/ (e.g., app/components/...). Use refactor-component for refactoring prompts and analyze-component for testing prompts and metrics.

cd web

Generate refactoring prompt

pnpm refactor-component

Output refactoring analysis as JSON

pnpm refactor-component --json

Generate testing prompt (after refactoring)

pnpm analyze-component

Output testing analysis as JSON

pnpm analyze-component --json

Complexity Analysis

Analyze component complexity

pnpm analyze-component --json

Key metrics to check:

- complexity: normalized score 0-100 (target < 50)

- maxComplexity: highest single function complexity

- lineCount: total lines (target < 300)

Complexity Score Interpretation Score Level Action 0-25 🟢 Simple Ready for testing 26-50 🟡 Medium Consider minor refactoring 51-75 🟠 Complex Refactor before testing 76-100 🔴 Very Complex Must refactor Core Refactoring Patterns Pattern 1: Extract Custom Hooks

When: Component has complex state management, multiple useState/useEffect, or business logic mixed with UI.

Dify Convention: Place hooks in a hooks/ subdirectory or alongside the component as use-.ts.

// ❌ Before: Complex state logic in component const Configuration: FC = () => { const [modelConfig, setModelConfig] = useState(...) const [datasetConfigs, setDatasetConfigs] = useState(...) const [completionParams, setCompletionParams] = useState({})

// 50+ lines of state management logic...

return

...
}

// ✅ After: Extract to custom hook // hooks/use-model-config.ts export const useModelConfig = (appId: string) => { const [modelConfig, setModelConfig] = useState(...) const [completionParams, setCompletionParams] = useState({})

// Related state management logic here

return { modelConfig, setModelConfig, completionParams, setCompletionParams } }

// Component becomes cleaner const Configuration: FC = () => { const { modelConfig, setModelConfig } = useModelConfig(appId) return

...
}

Dify Examples:

web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts web/app/components/app/configuration/debug/hooks.tsx web/app/components/workflow/hooks/use-workflow.ts Pattern 2: Extract Sub-Components

When: Single component has multiple UI sections, conditional rendering blocks, or repeated patterns.

Dify Convention: Place sub-components in subdirectories or as separate files in the same directory.

// ❌ Before: Monolithic JSX with multiple sections const AppInfo = () => { return (

{/ 100 lines of header UI /} {/ 100 lines of operations UI /} {/ 100 lines of modals /}
) }

// ✅ 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 (

setShowModal(null)} />
) }

Dify Examples:

web/app/components/app/configuration/ directory structure web/app/components/workflow/nodes/ per-node organization Pattern 3: Simplify Conditional Logic

When: Deep nesting (> 3 levels), complex ternaries, or multiple if/else chains.

// ❌ Before: Deeply nested conditionals const Template = useMemo(() => { if (appDetail?.mode === AppModeEnum.CHAT) { switch (locale) { case LanguagesSupported[1]: return case LanguagesSupported[7]: return default: return } } if (appDetail?.mode === AppModeEnum.ADVANCED_CHAT) { // Another 15 lines... } // More conditions... }, [appDetail, locale])

// ✅ After: Use lookup tables + early returns const TEMPLATE_MAP = {

[LanguagesSupported[1]]: TemplateChatZh,
[LanguagesSupported[7]]: TemplateChatJa,
default: TemplateChatEn,

},

[LanguagesSupported[1]]: TemplateAdvancedChatZh,
// ...

}, }

const Template = useMemo(() => { const modeTemplates = TEMPLATE_MAP[appDetail?.mode] if (!modeTemplates) return null

const TemplateComponent = modeTemplates[locale] || modeTemplates.default return }, [appDetail, locale])

Pattern 4: Extract API/Data Logic

When: Component directly handles API calls, data transformation, or complex async operations.

Dify Convention: Use @tanstack/react-query hooks from web/service/use-*.ts or create custom data hooks.

// ❌ 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(/apps/${appId}), select: data => data?.model_config || {}, }) }

// Component becomes cleaner const MCPServiceCard = () => { const { data: config, isLoading } = useAppConfig(appId, isBasicApp) // UI only }

React Query Best Practices in Dify:

Define NAME_SPACE for query key organization Use enabled option for conditional fetching Use select for data transformation Export invalidation hooks: useInvalidXxx

Dify Examples:

web/service/use-workflow.ts web/service/use-common.ts web/service/knowledge/use-dataset.ts web/service/knowledge/use-document.ts Pattern 5: Extract Modal/Dialog Management

When: Component manages multiple modals with complex open/close states.

Dify Convention: Modals should be extracted with their state management.

// ❌ 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(null)

const openModal = useCallback((type: ModalType) => setActiveModal(type), []) const closeModal = useCallback(() => setActiveModal(null), [])

return { activeModal, openModal, closeModal, isOpen: (type: ModalType) => activeModal === type, } }

Pattern 6: Extract Form Logic

When: Complex form validation, submission handling, or field transformation.

Dify Convention: Use @tanstack/react-form patterns from web/app/components/base/form/.

// ✅ Use existing form infrastructure import { useAppForm } from '@/app/components/base/form'

const ConfigForm = () => { const form = useAppForm({ defaultValues: { name: '', description: '' }, onSubmit: handleSubmit, })

return ... }

Dify-Specific Refactoring Guidelines 1. Context Provider Extraction

When: Component provides complex context values with multiple states.

// ❌ Before: Large context value object const value = { appId, isAPIKeySet, isTrailFinished, mode, modelModeType, promptMode, isAdvancedMode, isAgent, isOpenAI, isFunctionCall, // 50+ more properties... } return ...

// ✅ After: Split into domain-specific contexts {children}

Dify Reference: web/context/ directory structure

  1. Workflow Node Components

When: Refactoring workflow node components (web/app/components/workflow/nodes/).

Conventions:

Keep node logic in use-interactions.ts Extract panel UI to separate files Use _base components for common patterns nodes// ├── index.tsx # Node registration ├── node.tsx # Node visual component ├── panel.tsx # Configuration panel ├── use-interactions.ts # Node-specific hooks └── types.ts # Type definitions

  1. Configuration Components

When: Refactoring app configuration components.

Conventions:

Separate config sections into subdirectories Use existing patterns from web/app/components/app/configuration/ Keep feature toggles in dedicated components 4. Tool/Plugin Components

When: Refactoring tool-related components (web/app/components/tools/).

Conventions:

Follow existing modal patterns Use service hooks from web/service/use-tools.ts Keep provider-specific logic isolated Refactoring Workflow Step 1: Generate Refactoring Prompt pnpm refactor-component

This command will:

Analyze component complexity and features Identify specific refactoring actions needed Generate a prompt for AI assistant (auto-copied to clipboard on macOS) Provide detailed requirements based on detected patterns Step 2: Analyze Details pnpm analyze-component --json

Identify:

Total complexity score Max function complexity Line count Features detected (state, effects, API, etc.) Step 3: Plan

Create a refactoring plan based on detected features:

Detected Feature Refactoring Action hasState: true + hasEffects: true Extract custom hook hasAPI: true Extract data/service hook hasEvents: true (many) Extract event handlers lineCount > 300 Split into sub-components maxComplexity > 50 Simplify conditional logic Step 4: Execute Incrementally Extract one piece at a time Run lint, type-check, and tests after each extraction Verify functionality before next step 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 │ └────────────────────────────────────────┘

Step 5: Verify

After refactoring:

Re-run refactor command to verify improvements

pnpm refactor-component

If complexity < 25 and lines < 200, you'll see:

✅ COMPONENT IS WELL-STRUCTURED

For detailed metrics:

pnpm analyze-component --json

Target metrics:

- complexity < 50

- lineCount < 300

- maxComplexity < 30

Common Mistakes to Avoid ❌ Over-Engineering // ❌ 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 } }

❌ Breaking Existing Patterns Follow existing directory structures Maintain naming conventions Preserve export patterns for compatibility ❌ Premature Abstraction Only extract when there's clear complexity benefit Don't create abstractions for single-use code Keep refactored code in the same domain area References Dify Codebase Examples Hook extraction: web/app/components/app/configuration/hooks/ Component splitting: web/app/components/app/configuration/ Service hooks: web/service/use-*.ts Workflow patterns: web/app/components/workflow/hooks/ Form patterns: web/app/components/base/form/ Related Skills frontend-testing - For testing refactored components web/testing/testing.md - Testing specification

返回排行榜