mcpField
C
component-refactoring

component-refactoring

github.com/pageai-pro開発33373 回インストール17 週間前に更新
リポジトリ

component-refactoring について

Dify のコードベースで複雑な React コンポーネントを扱う開発者向けのリファクタリングスキルです。状態、UI、API 処理、モーダル管理を分離する方法を、既存の規約を保ちながら示します。

主な機能

  1. 01カスタムフックの抽出
  2. 02UI のサブコンポーネント化
  3. 03条件分岐の簡素化
  4. 04API とデータ処理の分離
  5. 05モーダル状態の集約

活用シーン

  1. 01コンポーネント内の状態と業務ロジックの整理
  2. 02大きな JSX セクションの分割
  3. 03コンポーネント内の API 処理の再構成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/pageai-pro/ralph-loop --skill component-refactoring

インストール後、Claude Code で/component-refactoringを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
complexity-patterns.md
component-splitting.md
hook-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 - リファクタリングしたコンポーネントのテストに使用します

コメント