fe-refactor

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

FE Refactoring

前端代码重构

$ARGUMENTS
로 전달된 파일의 코드를 분석하고 리팩토링한다.
分析并重构通过
$ARGUMENTS
传递的文件代码。

리팩토링 절차

重构步骤

  1. 현재 코드 분석: 대상 파일과 관련 파일을 읽고 구조를 파악한다
  2. 문제점 진단: 아래 패턴 목록에서 해당하는 항목을 식별한다
  3. 리팩토링 계획 제시: 변경 사항을 사용자에게 설명하고 승인을 받는다
  4. 리팩토링 실행: 승인된 변경 사항을 적용한다
  5. 검증: 변경 후 기존 테스트가 통과하는지 확인한다
  1. 当前代码分析:读取目标文件及相关文件,掌握代码结构
  2. 问题诊断:从下方模式列表中识别对应的问题项
  3. 提出重构计划:向用户说明变更内容并获得确认
  4. 执行重构:应用已确认的变更内容
  5. 验证:确认变更后原有测试是否能通过

리팩토링 패턴

重构模式

컴포넌트 분리

组件拆分

200줄 이상이거나 2개 이상의 책임을 가진 컴포넌트를 분리한다.
Before:
tsx
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:
tsx
function Dashboard() {
  return (
    <div>
      <DashboardHeader />
      <DashboardChart />
      <DashboardTable />
    </div>
  );
}
拆分代码行数超过200行或承担2个及以上职责的组件。
Before:
tsx
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:
tsx
function Dashboard() {
  return (
    <div>
      <DashboardHeader />
      <DashboardChart />
      <DashboardTable />
    </div>
  );
}

커스텀 훅 추출

自定义Hook提取

상태 + 이펙트 로직이 결합된 패턴을 훅으로 추출한다.
Before:
tsx
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:
tsx
// hooks/useProducts.ts
function useProducts() {
  return useQuery({
    queryKey: ["products"],
    queryFn: fetchProducts,
  });
}

// components/ProductList.tsx
function ProductList() {
  const { data: products, isLoading, error } = useProducts();
  // ... 렌더링
}
将状态与Effect逻辑结合的模式提取为Hook。
Before:
tsx
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:
tsx
// 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(派生状态)

useEffect
로 계산하는 파생 상태를
useMemo
또는 렌더링 중 계산으로 대체한다.
Before:
tsx
const [items, setItems] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const [search, setSearch] = useState("");

useEffect(() => {
  setFilteredItems(items.filter((i) => i.name.includes(search)));
}, [items, search]);
After:
tsx
const [items, setItems] = useState([]);
const [search, setSearch] = useState("");
const filteredItems = items.filter((i) => i.name.includes(search));
使用
useMemo
或在渲染过程中计算来替代通过
useEffect
计算的派生状态。
Before:
tsx
const [items, setItems] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const [search, setSearch] = useState("");

useEffect(() => {
  setFilteredItems(items.filter((i) => i.name.includes(search)));
}, [items, search]);
After:
tsx
const [items, setItems] = useState([]);
const [search, setSearch] = useState("");
const filteredItems = items.filter((i) => i.name.includes(search));

Server/Client Component 분리

Server/Client组件拆分

클라이언트 로직을 최소한의 Client Component로 격리한다.
Before:
tsx
"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:
tsx
// 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>
  );
}
将客户端逻辑隔离到最小化的Client Component中。
Before:
tsx
"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:
tsx
// 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 패턴

Compound Component模式

관련 컴포넌트들의 결합도가 높을 때 적용한다.
tsx
// 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>
在相关组件耦合度较高时使用该模式。
tsx
// 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>

타입 안전성 강화

增强类型安全性

tsx
// 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);
}
tsx
// 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 정리

Barrel Export整理

typescript
// components/user/index.ts
export { UserProfile } from "./UserProfile";
export { UserAvatar } from "./UserAvatar";
export { UserSettings } from "./UserSettings";
export type { UserProfileProps } from "./UserProfile";
typescript
// 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. 프로젝트의 기존 패턴과 일관성을 유지한다
  1. 若未传入参数,则询问用户重构目标
  2. 重构前必须先阅读并理解当前代码
  3. 若变更规模较大,则分阶段执行
  4. 若存在原有测试,需确认重构后测试是否正常运行
  5. 仅优化代码结构,不改变原有功能(保持行为一致)
  6. 保持与项目现有代码模式的一致性