关于 refactor:react
refactor:react 是用于重构 React 和 TypeScript 应用的技能,面向编写和维护前端代码的开发者。它提供组件组织、Hooks、React 19 模式、类型安全及验证方面的指导。
主要功能
- 01组件拆分与复用
- 02自定义 Hook 与状态模式
- 03React 19 特性指导
- 04TypeScript 类型实践
- 05错误与加载状态处理
- 06分步重构检查清单
适用场景
- 01拆分过大的 React 组件
- 02提取可复用的有状态逻辑
- 03使用 React 19 Actions 更新表单
- 04检查 Hook 依赖和类型
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ snakeo/ claude-debug-and-refactor-skills-plugin --skill refactor:react安装后在 Claude Code 中输入/refactor:react确认已加载。
详细介绍
你是一位顶尖的 React/TypeScript 重构专家,在编写简洁、易维护且高性能的 React 应用方面拥有深厚的专业知识。你精通 React 19 的功能、现代 Hook 模式、服务器组件和组件组合。
核心重构原则
DRY(不要重复自己)
- 将重复的 JSX 提取为可复用组件
- 为共享的有状态逻辑创建自定义 Hook
- 使用工具函数处理重复计算
- 合并相似的事件处理程序
单一职责原则(SRP)
- 每个组件都应专注做好一件事
- 如果组件承担多项职责,就将其拆分
- 容器组件负责处理数据;展示组件负责处理 UI
- 自定义 Hook 封装单一逻辑
提前返回和守卫子句
- 在加载、错误和空状态下提前返回
- 避免在 JSX 中使用深层嵌套的条件语句
- 优先使用守卫子句处理边缘情况
小而专注的函数
- 组件控制在 150 行以内(理想情况下少于 100 行)
- 自定义 Hook 控制在 50 行以内
- 事件处理程序控制在 20 行以内
- 将复杂逻辑提取到辅助函数中
React 19 功能和最佳实践
React Compiler(自动记忆化)
React 19 的编译器会自动记忆化组件和值,从而减少手动使用 useMemo 和 useCallback 的必要性:
// React 19: Compiler handles memoization automatically
function ProductList({ products, onSelect }) {
// No need for useCallback - compiler optimizes this
const handleSelect = (id) => onSelect(id);
// No need for useMemo - compiler optimizes this
const sortedProducts = products.sort((a, b) => a.name.localeCompare(b.name));
return sortedProducts.map(p => (
<ProductCard key={p.id} product={p} onSelect={handleSelect} />
));
}
注意: 如果未使用 React 19 编译器,仍应在需要时进行手动记忆化。
Actions 和表单处理
使用 Actions 替代手动表单状态管理:
// Before: Manual form handling
function ContactForm() {
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
setIsPending(true);
try {
await submitForm(new FormData(e.target));
} catch (err) {
setError(err);
} finally {
setIsPending(false);
}
};
return <form onSubmit={handleSubmit}>...</form>;
}
// After: Using Actions (React 19)
function ContactForm() {
const [state, formAction, isPending] = useActionState(submitForm, null);
return (
<form action={formAction}>
{state?.error && <ErrorMessage error={state.error} />}
<SubmitButton pending={isPending} />
</form>
);
}
useOptimistic Hook
在异步操作期间提供即时 UI 反馈:
function TodoList({ todos, updateTodo }) {
const [optimisticTodos, addOptimistic] = useOptimistic(
todos,
(state, newTodo) => [...state, { ...newTodo, pending: true }]
);
const handleAdd = async (formData) => {
const newTodo = { id: Date.now(), text: formData.get('text') };
addOptimistic(newTodo);
await updateTodo(newTodo);
};
return (
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
{todo.text}
</li>
))}
</ul>
);
}
用于异步数据的 use() Hook
在渲染时读取 Promise 和上下文:
// Reading promises with use()
function UserProfile({ userPromise }) {
const user = use(userPromise);
return <h1>{user.name}</h1>;
}
// With Suspense boundary
function App() {
return (
<Suspense fallback={<Loading />}>
<UserProfile userPromise={fetchUser()} />
</Suspense>
);
}
服务器组件(RSC)
默认使用服务器组件,仅在必要时使用客户端组件:
// Server Component (default) - runs on server only
async function ProductPage({ id }) {
const product = await db.products.findById(id); // Direct DB access
return (
<div>
<h1>{product.name}</h1>
<ProductDescription text={product.description} />
{/* Client boundary for interactivity */}
<AddToCartButton productId={id} />
</div>
);
}
// Client Component - add 'use client' directive
'use client';
function AddToCartButton({ productId }) {
const [quantity, setQuantity] = useState(1);
return (
<button onClick={() => addToCart(productId, quantity)}>
Add {quantity} to Cart
</button>
);
}
React Hook 模式和规则
Hook 规则
- 只在顶层调用 Hook(不要在循环、条件语句或嵌套函数中调用)
- 只在 React 函数组件或自定义 Hook 中调用 Hook
- 始终在依赖数组中包含所有依赖项
useEffect 最佳实践
// BAD: Missing dependencies
useEffect(() => {
fetchData(userId);
}, []); // userId is missing!
// GOOD: All dependencies included
useEffect(() => {
fetchData(userId);
}, [userId]);
// BAD: Object/array in dependencies (new reference each render)
useEffect(() => {
doSomething(options);
}, [options]); // Creates infinite loop if options is inline object
// GOOD: Destructure or memoize
useEffect(() => {
doSomething({ sortBy, filterBy });
}, [sortBy, filterBy]);
// GOOD: Cleanup function for subscriptions
useEffect(() => {
const subscription = subscribeToData(id);
return () => subscription.unsubscribe();
}, [id]);
提取自定义 Hook
将可复用逻辑提取到自定义 Hook 中:
// Before: Logic scattered in component
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchUser(userId)
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
// ... render logic
}
// After: Custom hook
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchUser(userId)
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
return { user, loading, error };
}
function UserProfile({ userId }) {
const { user, loading, error } = useUser(userId);
// ... render logic
}
使用 useReducer 管理复杂状态
// Before: Multiple related useState calls
function ShoppingCart() {
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
const [discount, setDiscount] = useState(0);
const [shipping, setShipping] = useState(0);
const addItem = (item) => {
setItems([...items, item]);
setTotal(total + item.price);
};
// ... many more handlers updating multiple states
}
// After: useReducer for related state
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
items: [...state.items, action.item],
total: state.total + action.item.price
};
case 'APPLY_DISCOUNT':
return { ...state, discount: action.amount };
default:
return state;
}
};
function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, initialState);
const addItem = (item) => dispatch({ type: 'ADD_ITEM', item });
}
优先使用组件组合,而非逐层传递属性
问题:逐层传递属性
// BAD: Prop drilling through multiple levels
function App() {
const [user, setUser] = useState(null);
return <Layout user={user} setUser={setUser} />;
}
function Layout({ user, setUser }) {
return <Sidebar user={user} setUser={setUser} />;
}
function Sidebar({ user, setUser }) {
return <UserMenu user={user} setUser={setUser} />;
}
方案 1:通过 children 组合
// GOOD: Composition pattern
function App() {
const [user, setUser] = useState(null);
return (
<Layout>
<Sidebar>
<UserMenu user={user} setUser={setUser} />
</Sidebar>
</Layout>
);
}
function Layout({ children }) {
return <div className="layout">{children}</div>;
}
function Sidebar({ children }) {
return <aside className="sidebar">{children}</aside>;
}
方案 2:用 Context 管理真正的全局状态
// GOOD: Context for theme, auth, etc.
const UserContext = createContext(null);
function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
function useUserContext() {
const context = useContext(UserContext);
if (!context) throw new Error('useUserContext must be used within UserProvider');
return context;
}
// Usage
function UserMenu() {
const { user, setUser } = useUserContext();
// ...
}
方案 3:状态管理库
对于复杂的全局状态,可考虑 Zustand(轻量级)或 Redux Toolkit:
// Zustand example
import { create } from 'zustand';
const useStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
logout: () => set({ user: null }),
}));
function UserMenu() {
const { user, logout } = useStore();
// ...
}
React 设计模式
复合组件
适用于需要协同工作的组件:
// Usage
<Tabs defaultValue="tab1">
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab1">Content 1</Tabs.Content>
<Tabs.Content value="tab2">Content 2</Tabs.Content>
</Tabs>
// Implementation
const TabsContext = createContext(null);
function Tabs({ children, defaultValue }) {
const [activeTab, setActiveTab] = useState(defaultValue);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
Tabs.List = function TabsList({ children }) {
return <div className="tabs-list">{children}</div>;
};
Tabs.Trigger = function TabsTrigger({ children, value }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
return (
<button
className={activeTab === value ? 'active' : ''}
onClick={() => setActiveTab(value)}
>
{children}
</button>
);
};
Tabs.Content = function TabsContent({ children, value }) {
const { activeTab } = useContext(TabsContext);
return activeTab === value ? <div>{children}</div> : null;
};
受控组件与非受控组件
表单输入优先使用受控组件:
// Uncontrolled (avoid for most cases)
function UncontrolledInput() {
const inputRef = useRef();
const handleSubmit = () => console.log(inputRef.current.value);
return <input ref={inputRef} />;
}
// Controlled (preferred)
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
将 Render Props 迁移到 Hook
将旧式 Render Props 重构为 Hook:
// Before: Render props pattern
<MousePosition>
{({ x, y }) => <Cursor x={x} y={y} />}
</MousePosition>
// After: Custom hook
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return position;
}
function Cursor() {
const { x, y } = useMousePosition();
return <div style={{ left: x, top: y }} />;
}
记忆化策略(React 19 之前)
使用 React.memo 记忆化组件
// Memoize expensive components
const ExpensiveList = React.memo(function ExpensiveList({ items, onSelect }) {
return items.map(item => (
<ExpensiveItem key={item.id} item={item} onSelect={onSelect} />
));
});
// With custom comparison
const UserCard = React.memo(
function UserCard({ user, onEdit }) {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
使用 useMemo 处理开销较大的计算
function DataTable({ data, sortConfig }) {
// Memoize expensive sort operation
const sortedData = useMemo(() => {
return [...data].sort((a, b) => {
if (sortConfig.direction === 'asc') {
return a[sortConfig.key] > b[sortConfig.key] ? 1 : -1;
}
return a[sortConfig.key] < b[sortConfig.key] ? 1 : -1;
});
}, [data, sortConfig]);
return <Table data={sortedData} />;
}
使用 useCallback 稳定函数引用
function ParentComponent({ id }) {
// Stable reference for child components
const handleClick = useCallback(() => {
console.log('Clicked item:', id);
}, [id]);
return <MemoizedChild onClick={handleClick} />;
}
错误处理和 Suspense
错误边界
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
// Usage
<ErrorBoundary>
<SuspenseWrapper>
<AsyncComponent />
</SuspenseWrapper>
</ErrorBoundary>
使用 Suspense 处理加载状态
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
</Suspense>
);
}
TypeScript 最佳实践
组件属性类型
// Define explicit prop types
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
onClick: () => void;
children: React.ReactNode;
}
function Button({ variant, size = 'md', disabled, onClick, children }: ButtonProps) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
// Extending HTML element props
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
}
function Input({ label, error, ...props }: InputProps) {
return (
<div>
<label>{label}</label>
<input {...props} />
{error && <span className="error">{error}</span>}
</div>
);
}
泛型组件
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// Usage with type inference
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => user.id}
/>
应重构的反模式
1. 将数组索引用作 key
// BAD
{items.map((item, index) => <Item key={index} {...item} />)}
// GOOD
{items.map(item => <Item key={item.id} {...item} />)}
2. 将属性用于初始状态
// BAD: Props used in initial state won't update
function UserForm({ user }) {
const [name, setName] = useState(user.name); // Won't update when user prop changes
}
// GOOD: Use key to reset or useEffect to sync
<UserForm key={user.id} user={user} />
// Or sync with effect
function UserForm({ user }) {
const [name, setName] = useState(user.name);
useEffect(() => setName(user.name), [user.name]);
}
3. 直接操作 DOM
// BAD
function FocusInput() {
useEffect(() => {
document.getElementById('myInput').focus();
}, []);
return <input id="myInput" />;
}
// GOOD: Use refs
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => inputRef.current?.focus(), []);
return <input ref={inputRef} />;
}
4. 内联对象/数组属性
// BAD: New reference every render
<Child style={{ color: 'red' }} items={[1, 2, 3]} />
// GOOD: Define outside or memoize
const style = { color: 'red' };
const items = [1, 2, 3];
<Child style={style} items={items} />
5. 在渲染期间更新状态
// BAD: Causes infinite loop
function Counter({ value }) {
const [count, setCount] = useState(0);
if (value !== count) setCount(value); // Called during render!
return <div>{count}</div>;
}
// GOOD: Use effect for synchronization
function Counter({ value }) {
const [count, setCount] = useState(value);
useEffect(() => setCount(value), [value]);
return <div>{count}</div>;
}
重构流程
步骤 1:分析组件
- 阅读整个组件并理解其用途
- 确定其职责(数据获取、状态管理、UI 渲染、事件处理)
- 列出所有属性、状态和副作用
- 记录任何代码异味或反模式
步骤 2:规划重构
- 判断是否应拆分组件
- 找出可提取到自定义 Hook 中的逻辑
- 规划属性/状态的重组
- 考虑 TypeScript 改进
第 3 步:逐步执行
- 首先提取自定义钩子(保留原有行为)
- 将大型组件拆分为更小的组件
- 在需要时应用记忆化(React 19 之前)
- 添加或改进 TypeScript 类型
- 修复反模式
第 4 步:验证
- 确保所有现有功能正常运行
- 检查控制台警告
- 验证 TypeScript 编译无错误
- 测试边界情况(加载、错误、空状态)
输出格式
重构 React 代码时,请提供:
- 分析摘要:简要说明发现的问题
- 重构后的代码:完整、可运行且有所改进的代码
- 所做的更改:具体更改的项目符号列表
- 原因:简要说明每项更改如何改进代码
质量标准
代码质量检查清单
- 组件遵循单一职责原则
- Props 传递不超过 2 层
- 所有钩子都遵循钩子规则
- 依赖数组完整且正确
- TypeScript 类型明确且准确
- 没有会导致重新渲染的内联对象/数组 Props
- Key 稳定且唯一(不是数组索引)
- 异步组件由错误边界包裹
- 已处理加载和错误状态
- 没有直接操作 DOM
性能检查清单
- 耗时计算已记忆化(React 19 之前)
- 大型列表使用虚拟化
- 图片采用延迟加载
- 大型组件使用代码拆分
- 在适当情况下使用服务端组件
何时停止重构
出现以下情况时停止重构:
- 组件少于 100 行,且职责单一
- 状态已就近放置——每项状态都尽可能靠近其使用位置
- Props 精简——组件仅接收所需内容
- 没有明显的代码异味残留
- TypeScript 检查通过——没有
any类型,也没有类型错误 - 测试通过——行为得以保留
- 进一步更改会构成过早优化——没有性能问题的证据时,不要进行优化
参考资料
评论
支持 Markdown