fe-refactor
SKILL.md
FE Refactoring
$ARGUMENTS로 전달된 파일의 코드를 분석하고 리팩토링한다.
리팩토링 절차
- 현재 코드 분석: 대상 파일과 관련 파일을 읽고 구조를 파악한다
- 문제점 진단: 아래 패턴 목록에서 해당하는 항목을 식별한다
- 리팩토링 계획 제시: 변경 사항을 사용자에게 설명하고 승인을 받는다
- 리팩토링 실행: 승인된 변경 사항을 적용한다
- 검증: 변경 후 기존 테스트가 통과하는지 확인한다
리팩토링 패턴
컴포넌트 분리
200줄 이상이거나 2개 이상의 책임을 가진 컴포넌트를 분리한다.
Before:
function Dashboard() {
// 사용자 데이터 로직
const [user, setUser] = useState(null);
useEffect(() => { fetchUser().then(setUser); }, []);
// 차트 데이터 로직
const [chartData, setChartData] = useState([]);
useEffect(() => { fetchChartData().then(setChartData); }, []);
return (
<div>
<header>{user?.name}</header>
<div>{/* 복잡한 차트 렌더링 */}</div>
<div>{/* 복잡한 테이블 렌더링 */}</div>
</div>
);
}
After:
function Dashboard() {
return (
<div>
<DashboardHeader />
<DashboardChart />
<DashboardTable />
</div>
);
}
커스텀 훅 추출
상태 + 이펙트 로직이 결합된 패턴을 훅으로 추출한다.
Before:
function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchProducts()
.then(setProducts)
.catch(setError)
.finally(() => setLoading(false));
}, []);
// ... 렌더링
}
After:
// hooks/useProducts.ts
function useProducts() {
return useQuery({
queryKey: ["products"],
queryFn: fetchProducts,
});
}
// components/ProductList.tsx
function ProductList() {
const { data: products, isLoading, error } = useProducts();
// ... 렌더링
}
useEffect 제거 (Derived State)
useEffect로 계산하는 파생 상태를 useMemo 또는 렌더링 중 계산으로 대체한다.
Before:
const [items, setItems] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const [search, setSearch] = useState("");
useEffect(() => {
setFilteredItems(items.filter((i) => i.name.includes(search)));
}, [items, search]);
After:
const [items, setItems] = useState([]);
const [search, setSearch] = useState("");
const filteredItems = items.filter((i) => i.name.includes(search));
Server/Client Component 분리
클라이언트 로직을 최소한의 Client Component로 격리한다.
Before:
"use client"; // 전체가 클라이언트
export default function ProductPage() {
const [count, setCount] = useState(0);
const products = useProducts(); // 서버에서 가져올 수 있는 데이터
return (
<div>
<h1>Products</h1>
<ProductList products={products} />
<Counter count={count} onChange={setCount} />
</div>
);
}
After:
// page.tsx (Server Component)
export default async function ProductPage() {
const products = await getProducts();
return (
<div>
<h1>Products</h1>
<ProductList products={products} />
<Counter /> {/* Client Component */}
</div>
);
}
Compound Component 패턴
관련 컴포넌트들의 결합도가 높을 때 적용한다.
// Before: props로 모든 것을 전달
<Select options={options} label="Country" placeholder="Select..." onChange={onChange} />
// After: Compound Component
<Select value={value} onValueChange={onChange}>
<SelectTrigger>
<SelectValue placeholder="Select..." />
</SelectTrigger>
<SelectContent>
{options.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
타입 안전성 강화
// Before: 느슨한 타입
function handleResponse(data: any) {
return data.items.map((item: any) => item.name);
}
// After: 엄격한 타입
interface ApiResponse {
items: Array<{ name: string; id: string }>;
}
function handleResponse(data: ApiResponse) {
return data.items.map((item) => item.name);
}
Barrel Export 정리
// components/user/index.ts
export { UserProfile } from "./UserProfile";
export { UserAvatar } from "./UserAvatar";
export { UserSettings } from "./UserSettings";
export type { UserProfileProps } from "./UserProfile";
실행 규칙
- 인자가 없으면 사용자에게 리팩토링 대상을 질문한다
- 리팩토링 전 반드시 현재 코드를 읽고 이해한다
- 변경 규모가 큰 경우 단계별로 나눠서 진행한다
- 기존 테스트가 있으면 리팩토링 후 깨지지 않는지 확인한다
- 기능 변경 없이 구조만 개선한다 (동작 보존)
- 프로젝트의 기존 패턴과 일관성을 유지한다
Weekly Installs
2
Repository
ingpdw/pdw-fe-dev-toolFirst Seen
Feb 7, 2026
Security Audits
Installed on
mcpjam2
openhands2
replit2
junie2
windsurf2
zencoder2