fe-refactor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFE Refactoring
前端代码重构
$ARGUMENTS分析并重构通过传递的文件代码。
$ARGUMENTS리팩토링 절차
重构步骤
- 현재 코드 분석: 대상 파일과 관련 파일을 읽고 구조를 파악한다
- 문제점 진단: 아래 패턴 목록에서 해당하는 항목을 식별한다
- 리팩토링 계획 제시: 변경 사항을 사용자에게 설명하고 승인을 받는다
- 리팩토링 실행: 승인된 변경 사항을 적용한다
- 검증: 변경 후 기존 테스트가 통과하는지 확인한다
- 当前代码分析:读取目标文件及相关文件,掌握代码结构
- 问题诊断:从下方模式列表中识别对应的问题项
- 提出重构计划:向用户说明变更内容并获得确认
- 执行重构:应用已确认的变更内容
- 验证:确认变更后原有测试是否能通过
리팩토링 패턴
重构模式
컴포넌트 분리
组件拆分
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(派生状态)
useEffectuseMemoBefore:
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));使用或在渲染过程中计算来替代通过计算的派生状态。
useMemouseEffectBefore:
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";실행 규칙
执行规则
- 인자가 없으면 사용자에게 리팩토링 대상을 질문한다
- 리팩토링 전 반드시 현재 코드를 읽고 이해한다
- 변경 규모가 큰 경우 단계별로 나눠서 진행한다
- 기존 테스트가 있으면 리팩토링 후 깨지지 않는지 확인한다
- 기능 변경 없이 구조만 개선한다 (동작 보존)
- 프로젝트의 기존 패턴과 일관성을 유지한다
- 若未传入参数,则询问用户重构目标
- 重构前必须先阅读并理解当前代码
- 若变更规模较大,则分阶段执行
- 若存在原有测试,需确认重构后测试是否正常运行
- 仅优化代码结构,不改变原有功能(保持行为一致)
- 保持与项目现有代码模式的一致性