name: component-patterns description: 实现React组件模式,包括组合、自定义钩子、渲染属性、高阶组件和复合组件。适用于构建可重用的React组件、实现设计模式或重构组件架构。
组件模式
实现React组件模式以构建可重用、可维护的组件。
快速开始
多数情况下使用组合模式,共享逻辑使用自定义钩子,灵活API使用复合组件。
指令
组合模式
构建灵活组件的默认模式。
基本组合:
function Card({ children }) {
return <div className="card">{children}</div>;
}
function CardHeader({ children }) {
return <div className="card-header">{children}</div>;
}
function CardBody({ children }) {
return <div className="card-body">{children}</div>;
}
// 用法
<Card>
<CardHeader>标题</CardHeader>
<CardBody>内容</CardBody>
</Card>
带属性:
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}
function Button({ variant = 'primary', size = 'md', children }: ButtonProps) {
return (
<button className={`btn btn-${variant} btn-${size}`}>
{children}
</button>
);
}
自定义钩子模式
跨组件提取和重用有状态逻辑。
基本自定义钩子:
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(v => !v);
}, []);
return [value, toggle] as const;
}
// 用法
function Component() {
const [isOpen, toggleOpen] = useToggle();
return <button onClick={toggleOpen}>{isOpen ? '关闭' : '打开'}</button>;
}
数据获取钩子:
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
表单钩子:
function useForm<T>(initialValues: T) {
const [values, setValues] = useState(initialValues);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValues(prev => ({
...prev,
[e.target.name]: e.target.value
}));
};
const reset = () => setValues(initialValues);
return { values, handleChange, reset };
}
复合组件模式
通过隐式状态共享创建灵活组件API。
基本复合组件:
interface TabsContextValue {
activeTab: string;
setActiveTab: (tab: string) => void;
}
const TabsContext = createContext<TabsContextValue | null>(null);
function Tabs({ children, defaultTab }: { children: React.ReactNode; defaultTab: string }) {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function TabList({ children }: { children: React.ReactNode }) {
return <div className="tab-list">{children}</div>;
}
function Tab({ id, children }: { id: string; children: React.ReactNode }) {
const context = useContext(TabsContext);
if (!context) throw new Error('Tab must be used within Tabs');
const { activeTab, setActiveTab } = context;
return (
<button
className={activeTab === id ? 'active' : ''}
onClick={() => setActiveTab(id)}
>
{children}
</button>
);
}
function TabPanel({ id, children }: { id: string; children: React.ReactNode }) {
const context = useContext(TabsContext);
if (!context) throw new Error('TabPanel must be used within Tabs');
if (context.activeTab !== id) return null;
return <div className="tab-panel">{children}</div>;
}
// 附加子组件
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;
// 用法
<Tabs defaultTab="home">
<Tabs.List>
<Tabs.Tab id="home">首页</Tabs.Tab>
<Tabs.Tab id="profile">个人资料</Tabs.Tab>
</Tabs.List>
<Tabs.Panel id="home">首页内容</Tabs.Panel>
<Tabs.Panel id="profile">个人资料内容</Tabs.Panel>
</Tabs>
渲染属性模式
通过函数属性提供渲染灵活性。
基本渲染属性:
interface MouseTrackerProps {
render: (position: { x: number; y: number }) => React.ReactNode;
}
function MouseTracker({ render }: MouseTrackerProps) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return <>{render(position)}</>;
}
// 用法
<MouseTracker
render={({ x, y }) => (
<div>鼠标位置 {x}, {y}</div>
)}
/>
作为函数的子元素:
interface DataProviderProps<T> {
url: string;
children: (data: T | null, loading: boolean) => React.ReactNode;
}
function DataProvider<T>({ url, children }: DataProviderProps<T>) {
const { data, loading } = useFetch<T>(url);
return <>{children(data, loading)}</>;
}
// 用法
<DataProvider url="/api/users">
{(users, loading) => (
loading ? <Spinner /> : <UserList users={users} />
)}
</DataProvider>
高阶组件(HOC)模式
包装组件以添加功能(传统模式,建议使用钩子)。
基本HOC:
function withLoading<P extends object>(
Component: React.ComponentType<P>
) {
return function WithLoadingComponent(props: P & { loading: boolean }) {
const { loading, ...rest } = props;
if (loading) return <Spinner />;
return <Component {...(rest as P)} />;
};
}
// 用法
const UserListWithLoading = withLoading(UserList);
<UserListWithLoading users={users} loading={loading} />
带额外属性的HOC:
function withAuth<P extends object>(
Component: React.ComponentType<P & { user: User }>
) {
return function WithAuthComponent(props: P) {
const { user, loading } = useAuth();
if (loading) return <Spinner />;
if (!user) return <Navigate to="/login" />;
return <Component {...props} user={user} />;
};
}
性能优化模式
React.memo
防止昂贵组件的不必要重新渲染。
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
// 昂贵渲染逻辑
return <div>{/* 复杂UI */}</div>;
});
// 自定义比较
const MemoizedComponent = React.memo(
function Component({ user }) {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
useMemo
记忆化昂贵计算。
function Component({ items }) {
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.value - b.value);
}, [items]);
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);
return <div>{/* 使用 sortedItems 和 total */}</div>;
}
useCallback
记忆化函数以防止子组件重新渲染。
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <MemoizedChild onClick={handleClick} />;
}
const MemoizedChild = React.memo(function Child({ onClick }) {
return <button onClick={onClick}>点击</button>;
});
代码分割
拆分代码以减少初始包大小。
const LazyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<LazyComponent />
</Suspense>
);
}
// 基于路由的分割
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Profile = React.lazy(() => import('./pages/Profile'));
<Routes>
<Route path="/dashboard" element={
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
} />
</Routes>
虚拟滚动
高效处理大型列表。
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map(virtualItem => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{items[virtualItem.index].name}
</div>
))}
</div>
</div>
);
}
常见模式
容器/展示组件模式
分离逻辑和展示。
// 展示组件
interface UserListProps {
users: User[];
onDelete: (id: string) => void;
}
function UserList({ users, onDelete }: UserListProps) {
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name}
<button onClick={() => onDelete(user.id)}>删除</button>
</li>
))}
</ul>
);
}
// 容器组件
function UserListContainer() {
const { data: users, isLoading } = useQuery(['users'], fetchUsers);
const deleteMutation = useMutation(deleteUser);
if (isLoading) return <Spinner />;
return <UserList users={users} onDelete={deleteMutation.mutate} />;
}
提供者模式
在组件树中共享数据。
interface ThemeContextValue {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = useCallback(() => {
setTheme(t => t === 'light' ? 'dark' : 'light');
}, []);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
}
受控与非受控组件
受控(推荐):
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={e => setValue(e.target.value)}
/>
);
}
非受控:
function UncontrolledInput() {
const inputRef = useRef<HTMLInputElement>(null);
const handleSubmit = () => {
console.log(inputRef.current?.value);
};
return <input ref={inputRef} />;
}
故障排除
不必要的重新渲染:
- 使用React DevTools性能分析器识别
- 用React.memo包装
- 适当使用useMemo/useCallback
- 检查状态是否提升过高
属性传递问题:
- 使用Context API处理深层嵌套属性
- 考虑组件组合
- 提取中间组件
钩子中的闭包问题:
- 为useEffect/useCallback添加依赖
- 使用函数式更新:
setState(prev => prev + 1) - 使用useRef处理可变值
内存泄漏:
- 在useEffect中清理订阅
- 卸载时取消待处理请求
- 移除事件监听器