fe-refactor

📁 ingpdw/pdw-fe-dev-tool 📅 6 days ago
1
总安装量
1
周安装量
#45781
全站排名
安装命令
npx skills add https://github.com/ingpdw/pdw-fe-dev-tool --skill fe-refactor

Agent 安装分布

mcpjam 1
claude-code 1
replit 1
junie 1
windsurf 1
zencoder 1

Skill 文档

FE Refactoring

$ARGUMENTS로 전달된 파일의 코드를 분석하고 리팩토링한다.

리팩토링 절차

  1. 현재 코드 분석: 대상 파일과 관련 파일을 읽고 구조를 파악한다
  2. 문제점 진단: 아래 패턴 목록에서 해당하는 항목을 식별한다
  3. 리팩토링 계획 제시: 변경 사항을 사용자에게 설명하고 승인을 받는다
  4. 리팩토링 실행: 승인된 변경 사항을 적용한다
  5. 검증: 변경 후 기존 테스트가 통과하는지 확인한다

리팩토링 패턴

컴포넌트 분리

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";

실행 규칙

  1. 인자가 없으면 사용자에게 리팩토링 대상을 질문한다
  2. 리팩토링 전 반드시 현재 코드를 읽고 이해한다
  3. 변경 규모가 큰 경우 단계별로 나눠서 진행한다
  4. 기존 테스트가 있으면 리팩토링 후 깨지지 않는지 확인한다
  5. 기능 변경 없이 구조만 개선한다 (동작 보존)
  6. 프로젝트의 기존 패턴과 일관성을 유지한다