component-refactoring について
Dify のコードベースで複雑な React コンポーネントを扱う開発者向けのリファクタリングスキルです。状態、UI、API 処理、モーダル管理を分離する方法を、既存の規約を保ちながら示します。
主な機能
- 01カスタムフックの抽出
- 02UI のサブコンポーネント化
- 03条件分岐の簡素化
- 04API とデータ処理の分離
- 05モーダル状態の集約
活用シーン
- 01コンポーネント内の状態と業務ロジックの整理
- 02大きな JSX セクションの分割
- 03コンポーネント内の API 処理の再構成
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ pageai-pro/ ralph-loop --skill component-refactoringインストール後、Claude Code で/component-refactoringを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/complexity-patterns.mdcomponent-splitting.mdhook-extraction.md詳細
コンポーネントのリファクタリングスキル
以下のパターンとワークフローを使用して、複雑度の高い React コンポーネントをリファクタリングします。
リファクタリングの基本パターン
パターン 1:カスタムフックの抽出
使用する場面:コンポーネントの状態管理が複雑な場合、useState/useEffect が複数ある場合、またはビジネスロジックと UI が混在している場合。
Dify の規約:フックは 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>
}
パターン 2:サブコンポーネントの抽出
使用する場面:単一のコンポーネントに複数の UI セクション、条件付きレンダリングブロック、または繰り返しのパターンがある場合。
// ❌ 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>
)
}
パターン 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 呼び出し、データ変換、または複雑な非同期処理を直接扱っている場合。
// ❌ 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
}
React Query のベストプラクティス:
- クエリーキーを整理するために
NAME_SPACEを定義します - 条件付きフェッチには
enabledオプションを使用します - データ変換には
selectを使用します - 無効化フックとして
useInvalidXxxをエクスポートします
パターン 5:モーダル/ダイアログ管理の抽出
使用する場面:コンポーネントが、開閉状態の複雑な複数のモーダルを管理している場合。
// ❌ 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,
}
}
避けるべきよくある間違い
❌ 過剰なエンジニアリング
// ❌ 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 }
}
❌ 既存のパターンを崩す
- 既存のディレクトリ構造に従います
- 命名規則を維持します
- 互換性を保つため、エクスポートのパターンを維持します
❌ 時期尚早な抽象化
- 複雑さを明確に軽減できる場合にのみ抽出します
- 一度しか使用しないコードのために抽象化を作成しないでください
- リファクタリングしたコードは同じドメイン領域に保ちます
参考資料
関連スキル
frontend-testing- リファクタリングしたコンポーネントのテストに使用します
コメント
Markdown 対応