component-refactoring について
Langflowのフロントエンドにある複雑なReactコンポーネントをリファクタリングするための開発スキルです。複雑度の評価、分割パターン、段階的な検証手順を開発者に提供します。
主な機能
- 01コンポーネント複雑度の手動評価
- 02カスタムフックとサブコンポーネントの抽出
- 03条件分岐とフォーム処理の簡素化
- 04Langflowのディレクトリと状態規約への準拠
- 05変更を段階的に検証
活用シーン
- 01300行を超えるコンポーネントの分割
- 02複雑な状態と副作用の整理
- 03深くネストした条件分岐のリファクタリング
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ langflow-ai/ langflow --skill component-refactoringインストール後、Claude Code で/component-refactoringを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/complexity-patterns.mdcomponent-splitting.mdhook-extraction.md詳細
Langflow コンポーネントのリファクタリングスキル
以下のパターンとワークフローに従って、Langflow のフロントエンドコードベースにある複雑度の高い React コンポーネントをリファクタリングします。
複雑度のしきい値:複雑度が 50 を超えるコンポーネント(条件分岐、ネストの深さ、行数を手作業で数えて測定)は、テスト前にリファクタリングしてください。
クイックリファレンス
コマンド(src/frontend/ から実行)
cd src/frontend
# Lint with Biome
npm run lint
# Type checking
npm run type-check
# Run tests
npm test
複雑度の手動評価
Langflow には自動複雑度分析ツールがないため、コンポーネントを手動で評価します。
- 条件分岐を数える:各
if/else、switch/case、三項演算子、&&/||の連鎖に +1 を加えます。 - ネストの深さを数える:条件分岐またはループ内のネストが 1 段深くなるごとに +1 を加えます。
- 総行数を数える:コンポーネントファイルあたり 300 行未満を目標にします。
- ステートフックを数える:
useStateの呼び出しが 5 回を超える場合は、フックの抽出を検討してください。 - エフェクトを数える:
useEffectの呼び出しが 3 回を超える場合は、エフェクトの統合を検討してください。
複雑度スコアの解釈
| スコア | レベル | 対応 |
|---|---|---|
| 0-25 | シンプル | テスト可能 |
| 26-50 | 中程度 | 小規模なリファクタリングを検討 |
| 51-75 | 複雑 | テスト前にリファクタリング |
| 76-100 | 非常に複雑 | 必ずリファクタリング |
主なリファクタリングパターン
パターン 1:カスタムフックを抽出する
タイミング:コンポーネントに複雑なステート管理、複数の useState/useEffect、または UI と混在したビジネスロジックがある場合。
Langflow の規約:フックは hooks/ サブディレクトリに配置するか、コンポーネントと同じ場所に use-<feature>.ts として配置します。Langflow では use- プレフィックスを付けたケバブケースのファイル名を使用します。
// Before: Complex state logic in component
const FlowPage: FC = () => {
const [nodes, setNodes] = useState<Node[]>([])
const [edges, setEdges] = useState<Edge[]>([])
const [buildStatus, setBuildStatus] = useState<BuildStatus>(BuildStatus.IDLE)
// 50+ lines of state management logic...
return <div>...</div>
}
// After: Extract to custom hook
// hooks/use-flow-state.ts
export const useFlowState = (flowId: string) => {
const [nodes, setNodes] = useState<Node[]>([])
const [edges, setEdges] = useState<Edge[]>([])
const [buildStatus, setBuildStatus] = useState<BuildStatus>(BuildStatus.IDLE)
// Related state management logic here
return { nodes, setNodes, edges, setEdges, buildStatus, setBuildStatus }
}
// Component becomes cleaner
const FlowPage: FC = () => {
const { nodes, setNodes, edges, setEdges } = useFlowState(flowId)
return <div>...</div>
}
Langflow の例:
src/frontend/src/hooks/use-add-component.tssrc/frontend/src/hooks/use-unsaved-changes.tssrc/frontend/src/hooks/use-refresh-model-inputs.ts
パターン 2:サブコンポーネントを抽出する
タイミング:単一のコンポーネントに複数の UI セクション、条件付きレンダリングブロック、または繰り返しのパターンがある場合。
Langflow の規約:サブコンポーネントはサブディレクトリ、または同じディレクトリ内の別ファイルに配置します。UI プリミティブは components/ui/、ドメインコンポーネントは components/core/、再利用可能なコンポーネントは components/common/ に配置します。
// Before: Monolithic JSX with multiple sections
const GenericNode = () => {
return (
<div>
{/* 100 lines of header UI */}
{/* 100 lines of parameter fields */}
{/* 100 lines of output handles */}
</div>
)
}
// After: Split into focused components
// CustomNodes/GenericNode/
// generic-node.tsx (orchestration only — kebab-case, descriptive name)
// components/
// node-header.tsx
// node-parameters.tsx
// node-outputs.tsx
const GenericNode = () => {
return (
<div>
<NodeHeader nodeData={data} />
<NodeParameters fields={fields} />
<NodeOutputs outputs={outputs} />
</div>
)
}
Langflow の例:
src/frontend/src/CustomNodes/GenericNode/components/src/frontend/src/components/core/src/frontend/src/components/ui/
パターン 3:条件分岐を簡素化する
タイミング:ネストが深い(3 段を超える)、三項演算子が複雑、または if/else の連鎖が複数ある場合。
// Before: Deeply nested conditionals
const getFieldComponent = (field: InputFieldType) => {
if (field.type === "str") {
if (field.multiline) {
return <TextAreaComponent />
} else if (field.password) {
return <PasswordInput />
} else if (field.options?.length) {
return <Dropdown options={field.options} />
} else {
return <InputComponent />
}
} else if (field.type === "int") {
return <IntComponent />
} else if (field.type === "float") {
return <FloatComponent />
}
return null
}
// After: Use lookup tables + early returns
const FIELD_COMPONENT_MAP: Record<string, FC<FieldProps>> = {
int: IntComponent,
float: FloatComponent,
bool: ToggleComponent,
code: CodeAreaComponent,
}
const STR_VARIANT_MAP: Record<string, FC<FieldProps>> = {
multiline: TextAreaComponent,
password: PasswordInput,
options: Dropdown,
}
const getFieldComponent = (field: InputFieldType) => {
if (field.type !== "str") {
const Component = FIELD_COMPONENT_MAP[field.type]
return Component ? <Component {...field} /> : null
}
const variant = field.multiline ? "multiline"
: field.password ? "password"
: field.options?.length ? "options"
: "default"
const Component = STR_VARIANT_MAP[variant] ?? InputComponent
return <Component {...field} />
}
パターン 4:API/データロジックを抽出する
タイミング:コンポーネントが API 呼び出し、データ変換、または複雑な非同期処理を直接扱っている場合。
Langflow の規約:
- このスキルはコンポーネントの分解を対象としており、クエリ/ミューテーションの設計を対象としていません。
- データ取得をリファクタリングする際は、クエリパターン、
UseRequestProcessor、キャッシュの無効化、ミューテーションのエラー処理についてfrontend-query-mutationを使用します。 - リファクタリング中に、処理をそのまま通すだけの薄い
useQueryラッパーを作成しないでください。複数のクエリ/ミューテーションや共有する派生ステートを実際に調整する場合に限り、カスタムフックを抽出します。 - API フックは
controllers/API/queries/{domain}/に配置します。
Langflow の例:
src/frontend/src/controllers/API/queries/flows/use-post-add-flow.tssrc/frontend/src/controllers/API/queries/variables/use-get-global-variables.tssrc/frontend/src/controllers/API/queries/folders/use-get-folders.ts
パターン 5:モーダル/ダイアログの管理を抽出する
タイミング:コンポーネントが、複雑な開閉状態を持つ複数のモーダルを管理している場合。
Langflow の規約:モーダルは状態管理とともに抽出します。
// Before: Multiple modal states in component
const FlowToolbar = () => {
const [showExportModal, setShowExportModal] = useState(false)
const [showShareModal, setShowShareModal] = useState(false)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const [showApiModal, setShowApiModal] = useState(false)
// 5+ more modal states...
}
// After: Extract to modal management hook
type ModalType = "export" | "share" | "delete" | "api" | null
const useFlowToolbarModals = () => {
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:フォームロジックを抽出する
タイミング:フォームのバリデーション、送信処理、またはフィールド変換が複雑な場合。
Langflow の規約:フォームの状態とバリデーションはフックに抽出します。
// Extract form validation and submission
const useFlowSettingsForm = (initialValues: FlowSettings) => {
const [values, setValues] = useState(initialValues)
const [errors, setErrors] = useState<Record<string, string>>({})
const validate = useCallback(() => {
const newErrors: Record<string, string> = {}
if (!values.name?.trim()) newErrors.name = "Name is required"
if (values.endpoint_name && !/^[a-z0-9-]+$/.test(values.endpoint_name)) {
newErrors.endpoint_name = "Must be lowercase alphanumeric with hyphens"
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}, [values])
const handleChange = useCallback((field: string, value: any) => {
setValues((prev) => ({ ...prev, [field]: value }))
}, [])
return { values, errors, validate, handleChange }
}
Langflow 固有のリファクタリングガイドライン
1. Zustand ストアセレクター
対象:コンポーネントが Zustand ストアから多くの値を読み取り、不要な再レンダリングが発生している場合。
// Before: Selecting too many values
const Component = () => {
const flowStore = useFlowStore()
// Component re-renders on ANY store change
}
// After: Use individual selectors
const Component = () => {
const nodes = useFlowStore((state) => state.nodes)
const edges = useFlowStore((state) => state.edges)
// Component only re-renders when nodes or edges change
}
Langflow の参考例:src/frontend/src/stores/ 内のすべてのストアで、このセレクターパターンに従っています。
2. カスタムノードコンポーネント
対象:フローノードコンポーネント(CustomNodes/GenericNode/)をリファクタリングする場合。
規約:
- ノードのロジックはカスタムフックにまとめます
- パラメーターのレンダリングは別のコンポーネントに切り出します
- サブコンポーネントには既存の
components/サブディレクトリを使用します
CustomNodes/GenericNode/
generic-node.tsx # Node registration and main render (kebab-case, NOT index.tsx)
components/
handle-render.tsx # Handle rendering
node-description.tsx # Node description display
node-input-field.tsx # Input field rendering
node-name.tsx # Node name display
node-output-field.tsx # Output field rendering
node-status.tsx # Build status display
3. フローキャンバスコンポーネント
対象:フローエディターのキャンバスに関連するコンポーネントをリファクタリングする場合。
規約:
- キャンバスライブラリには
@xyflow/reactv12 を使用します - キャンバスのイベントハンドラーと UI のレンダリングを分離します
- フローの状態管理には
useFlowStoreを使用します - 複数のフローの管理には
useFlowsManagerStoreを使用します
4. API クエリフックを使用するコンポーネント
対象:API データを使用するコンポーネントをリファクタリングする場合。
規約:
controllers/API/queries/にある既存のクエリフックを使用します- 新しいクエリ/ミューテーションには
UseRequestProcessorを使用します - キャッシュ管理には
["useGetGlobalVariables"]のようなクエリキーの配列を使用します - キャッシュの無効化はミューテーションの
onSettledコールバック内で行います
リファクタリングのワークフロー
ステップ 1:複雑さを評価する
以下を手動で数えます:
- 条件分岐の総数(if/else、switch、三項演算子、&&/||)
- ネストの最大深度
- コードの総行数
- useState/useEffect フックの数
- 個別の UI セクションの数
ステップ 2:計画する
検出した特徴に基づいて、リファクタリング計画を作成します。
| 検出した特徴 | リファクタリングの対応 |
|---|---|
関連する状態を扱う useState フックが 5 つ以上 | カスタムフックに切り出す |
| コンポーネント本体内の API 呼び出し | クエリフックに切り出す |
| ロジックを含むイベントハンドラーが 3 つ以上 | イベントハンドラーをフックに切り出す |
| 300 行以上 | サブコンポーネントに分割する |
| 条件分岐のネストが深い(3 段階超) | 条件分岐のロジックを簡潔にする |
| モーダルの状態が複数ある | モーダル管理を切り出す |
ステップ 3:段階的に実行する
- 一度に 1 つずつ切り出します
- 切り出すたびに lint、型チェック、テストを実行します
- 次のステップに進む前に機能を確認します
For each extraction:
1. Extract code
2. Run: npm run lint
3. Run: npm run type-check
4. Run: npm test
5. Test functionality manually
6. PASS? -> Next extraction
FAIL? -> Fix before continuing
ステップ 4:確認する
リファクタリング後、複雑さを手動で再評価します。
- 複雑さの目標値:50 未満
- 行数の目標値:300 未満
- ネストの最大深度の目標値:3 以下
- 関数の最大行数の目標値: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 }
}
既存のパターンを崩す
components/ui/、components/core/、components/common/の既存のディレクトリ構成に従います- 命名規則を維持します(ファイル名は kebab-case、コンポーネント名は PascalCase)
- 互換性を保つため、エクスポートのパターンを維持します
- Zustand ストアセレクターのパターンに一貫性を持たせます
早すぎる抽象化
- 複雑さを明確に軽減できる場合にのみ切り出します
- 一度しか使わないコードのために抽象化を作成しないでください
- リファクタリングしたコードは、同じドメイン領域内に保ちます
UseRequestProcessor を迂回する
- API 呼び出しに
useQueryやuseMutationを直接使用しないでください - リトライと無効化のロジックに一貫性を持たせるため、必ず
UseRequestProcessorパターンを使用します - API フックのパターンについては、
frontend-query-mutationスキルを参照してください
参考資料
Langflow のコードベースの例
- フックの切り出し:
src/frontend/src/hooks/ - コンポーネントの分割:
src/frontend/src/CustomNodes/GenericNode/components/ - UI コンポーネント:
src/frontend/src/components/ui/ - コアコンポーネント:
src/frontend/src/components/core/ - 共通コンポーネント:
src/frontend/src/components/common/ - API クエリフック:
src/frontend/src/controllers/API/queries/ - Zustand ストア:
src/frontend/src/stores/
関連スキル
frontend-query-mutation- API クエリおよびミューテーションのパターンについてfrontend-testing- リファクタリングしたコンポーネントのテストについて