<< All versions
Skill v1.0.0
currentLLM-judged scan90/100virgo777/buddyme/frontend-patterns
──Details
PublishedOctober 23, 2026 at 06:28 AM
Content Hashsha256:f5be9dd72e6e26a8...
Git SHA40fd6b4635ec
──Files
Files (1 file, 14.9 KB)
SKILL.md14.9 KBactive
SKILL.md · 644 lines · 14.9 KB
version: "1.0.0" name: frontend-patterns description: 针对 React、Next.js、状态管理、性能优化及 UI 最佳实践的前端开发模式。 origin: ECC
前端开发模式 (Frontend Development Patterns)
适用于 React、Next.js 和高性能用户界面的现代前端模式。
何时激活
- 构建 React 组件(组合、Props、渲染)
- 管理状态(useState、useReducer、Zustand、Context)
- 实现数据获取(SWR、React Query、服务端组件)
- 优化性能(记忆化、虚拟化、代码分割)
- 处理表单(验证、受控输入、Zod 模式)
- 处理客户端路由与导航
- 构建可访问(Accessible)、响应式的 UI 模式
组件模式 (Component Patterns)
组合优于继承 (Composition Over Inheritance)
typescript
// ✅ 推荐:组件组合interface CardProps {children: React.ReactNodevariant?: 'default' | 'outlined'}export function Card({ children, variant = 'default' }: CardProps) {return <div className={`card card-${variant}`}>{children}</div>}export function CardHeader({ children }: { children: React.ReactNode }) {return <div className="card-header">{children}</div>}export function CardBody({ children }: { children: React.ReactNode }) {return <div className="card-body">{children}</div>}// 使用示例<Card><CardHeader>Title</CardHeader><CardBody>Content</CardBody></Card>
复合组件 (Compound Components)
typescript
interface TabsContextValue {activeTab: stringsetActiveTab: (tab: string) => void}const TabsContext = createContext<TabsContextValue | undefined>(undefined)export function Tabs({ children, defaultTab }: {children: React.ReactNodedefaultTab: string}) {const [activeTab, setActiveTab] = useState(defaultTab)return (<TabsContext.Provider value={{ activeTab, setActiveTab }}>{children}</TabsContext.Provider>)}export function TabList({ children }: { children: React.ReactNode }) {return <div className="tab-list">{children}</div>}export function Tab({ id, children }: { id: string, children: React.ReactNode }) {const context = useContext(TabsContext)if (!context) throw new Error('Tab must be used within Tabs')return (<buttonclassName={context.activeTab === id ? 'active' : ''}onClick={() => context.setActiveTab(id)}>{children}</button>)}// 使用示例<Tabs defaultTab="overview"><TabList><Tab id="overview">Overview</Tab><Tab id="details">Details</Tab></TabList></Tabs>
Render Props 模式 (Render Props Pattern)
typescript
interface DataLoaderProps<T> {url: stringchildren: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode}export function DataLoader<T>({ url, children }: DataLoaderProps<T>) {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 <>{children(data, loading, error)}</>}// 使用示例<DataLoader<Market[]> url="/api/markets">{(markets, loading, error) => {if (loading) return <Spinner />if (error) return <Error error={error} />return <MarketList markets={markets!} />}}</DataLoader>
自定义 Hook 模式 (Custom Hooks Patterns)
状态管理 Hook (State Management Hook)
typescript
export function useToggle(initialValue = false): [boolean, () => void] {const [value, setValue] = useState(initialValue)const toggle = useCallback(() => {setValue(v => !v)}, [])return [value, toggle]}// 使用示例const [isOpen, toggleOpen] = useToggle()
异步数据获取 Hook (Async Data Fetching Hook)
typescript
interface UseQueryOptions<T> {onSuccess?: (data: T) => voidonError?: (error: Error) => voidenabled?: boolean}export function useQuery<T>(key: string,fetcher: () => Promise<T>,options?: UseQueryOptions<T>) {const [data, setData] = useState<T | null>(null)const [error, setError] = useState<Error | null>(null)const [loading, setLoading] = useState(false)const refetch = useCallback(async () => {setLoading(true)setError(null)try {const result = await fetcher()setData(result)options?.onSuccess?.(result)} catch (err) {const error = err as ErrorsetError(error)options?.onError?.(error)} finally {setLoading(false)}}, [fetcher, options])useEffect(() => {if (options?.enabled !== false) {refetch()}}, [key, refetch, options?.enabled])return { data, error, loading, refetch }}// 使用示例const { data: markets, loading, error, refetch } = useQuery('markets',() => fetch('/api/markets').then(r => r.json()),{onSuccess: data => console.log('Fetched', data.length, 'markets'),onError: err => console.error('Failed:', err)})
防抖 Hook (Debounce Hook)
typescript
export function useDebounce<T>(value: T, delay: number): T {const [debouncedValue, setDebouncedValue] = useState<T>(value)useEffect(() => {const handler = setTimeout(() => {setDebouncedValue(value)}, delay)return () => clearTimeout(handler)}, [value, delay])return debouncedValue}// 使用示例const [searchQuery, setSearchQuery] = useState('')const debouncedQuery = useDebounce(searchQuery, 500)useEffect(() => {if (debouncedQuery) {performSearch(debouncedQuery)}}, [debouncedQuery])
状态管理模式 (State Management Patterns)
Context + Reducer 模式 (Context + Reducer Pattern)
typescript
interface State {markets: Market[]selectedMarket: Market | nullloading: boolean}type Action =| { type: 'SET_MARKETS'; payload: Market[] }| { type: 'SELECT_MARKET'; payload: Market }| { type: 'SET_LOADING'; payload: boolean }function reducer(state: State, action: Action): State {switch (action.type) {case 'SET_MARKETS':return { ...state, markets: action.payload }case 'SELECT_MARKET':return { ...state, selectedMarket: action.payload }case 'SET_LOADING':return { ...state, loading: action.payload }default:return state}}const MarketContext = createContext<{state: Statedispatch: Dispatch<Action>} | undefined>(undefined)export function MarketProvider({ children }: { children: React.ReactNode }) {const [state, dispatch] = useReducer(reducer, {markets: [],selectedMarket: null,loading: false})return (<MarketContext.Provider value={{ state, dispatch }}>{children}</MarketContext.Provider>)}export function useMarkets() {const context = useContext(MarketContext)if (!context) throw new Error('useMarkets must be used within MarketProvider')return context}
性能优化 (Performance Optimization)
记忆化 (Memoization)
typescript
// ✅ 使用 useMemo 处理高开销计算const sortedMarkets = useMemo(() => {return markets.sort((a, b) => b.volume - a.volume)}, [markets])// ✅ 使用 useCallback 处理传递给子组件的函数const handleSearch = useCallback((query: string) => {setSearchQuery(query)}, [])// ✅ 使用 React.memo 处理纯组件export const MarketCard = React.memo<MarketCardProps>(({ market }) => {return (<div className="market-card"><h3>{market.name}</h3><p>{market.description}</p></div>)})
代码分割与懒加载 (Code Splitting & Lazy Loading)
typescript
import { lazy, Suspense } from 'react'// ✅ 懒加载重型组件const HeavyChart = lazy(() => import('./HeavyChart'))const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))export function Dashboard() {return (<div><Suspense fallback={<ChartSkeleton />}><HeavyChart data={data} /></Suspense><Suspense fallback={null}><ThreeJsBackground /></Suspense></div>)}
长列表虚拟化 (Virtualization for Long Lists)
typescript
import { useVirtualizer } from '@tanstack/react-virtual'export function VirtualMarketList({ markets }: { markets: Market[] }) {const parentRef = useRef<HTMLDivElement>(null)const virtualizer = useVirtualizer({count: markets.length,getScrollElement: () => parentRef.current,estimateSize: () => 100, // 预估行高overscan: 5 // 额外渲染的项目数量})return (<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}><divstyle={{height: `${virtualizer.getTotalSize()}px`,position: 'relative'}}>{virtualizer.getVirtualItems().map(virtualRow => (<divkey={virtualRow.index}style={{position: 'absolute',top: 0,left: 0,width: '100%',height: `${virtualRow.size}px`,transform: `translateY(${virtualRow.start}px)`}}><MarketCard market={markets[virtualRow.index]} /></div>))}</div></div>)}
表单处理模式 (Form Handling Patterns)
带验证的受控表单 (Controlled Form with Validation)
typescript
interface FormData {name: stringdescription: stringendDate: string}interface FormErrors {name?: stringdescription?: stringendDate?: string}export function CreateMarketForm() {const [formData, setFormData] = useState<FormData>({name: '',description: '',endDate: ''})const [errors, setErrors] = useState<FormErrors>({})const validate = (): boolean => {const newErrors: FormErrors = {}if (!formData.name.trim()) {newErrors.name = 'Name is required'} else if (formData.name.length > 200) {newErrors.name = 'Name must be under 200 characters'}if (!formData.description.trim()) {newErrors.description = 'Description is required'}if (!formData.endDate) {newErrors.endDate = 'End date is required'}setErrors(newErrors)return Object.keys(newErrors).length === 0}const handleSubmit = async (e: React.FormEvent) => {e.preventDefault()if (!validate()) returntry {await createMarket(formData)// 成功处理} catch (error) {// 错误处理}}return (<form onSubmit={handleSubmit}><inputvalue={formData.name}onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}placeholder="Market name"/>{errors.name && <span className="error">{errors.name}</span>}{/* 其他字段 */}<button type="submit">Create Market</button></form>)}
错误边界模式 (Error Boundary Pattern)
typescript
interface ErrorBoundaryState {hasError: booleanerror: Error | null}export class ErrorBoundary extends React.Component<{ children: React.ReactNode },ErrorBoundaryState> {state: ErrorBoundaryState = {hasError: false,error: null}static getDerivedStateFromError(error: Error): ErrorBoundaryState {return { hasError: true, error }}componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {console.error('Error boundary caught:', error, errorInfo)}render() {if (this.state.hasError) {return (<div className="error-fallback"><h2>Something went wrong</h2><p>{this.state.error?.message}</p><button onClick={() => this.setState({ hasError: false })}>Try again</button></div>)}return this.props.children}}// 使用示例<ErrorBoundary><App /></ErrorBoundary>
动画模式 (Animation Patterns)
Framer Motion 动画 (Framer Motion Animations)
typescript
import { motion, AnimatePresence } from 'framer-motion'// ✅ 列表动画export function AnimatedMarketList({ markets }: { markets: Market[] }) {return (<AnimatePresence>{markets.map(market => (<motion.divkey={market.id}initial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}exit={{ opacity: 0, y: -20 }}transition={{ duration: 0.3 }}><MarketCard market={market} /></motion.div>))}</AnimatePresence>)}// ✅ 弹窗动画export function Modal({ isOpen, onClose, children }: ModalProps) {return (<AnimatePresence>{isOpen && (<><motion.divclassName="modal-overlay"initial={{ opacity: 0 }}animate={{ opacity: 1 }}exit={{ opacity: 0 }}onClick={onClose}/><motion.divclassName="modal-content"initial={{ opacity: 0, scale: 0.9, y: 20 }}animate={{ opacity: 1, scale: 1, y: 0 }}exit={{ opacity: 0, scale: 0.9, y: 20 }}>{children}</motion.div></>)}</AnimatePresence>)}
可访问性模式 (Accessibility Patterns)
键盘导航 (Keyboard Navigation)
typescript
export function Dropdown({ options, onSelect }: DropdownProps) {const [isOpen, setIsOpen] = useState(false)const [activeIndex, setActiveIndex] = useState(0)const handleKeyDown = (e: React.KeyboardEvent) => {switch (e.key) {case 'ArrowDown':e.preventDefault()setActiveIndex(i => Math.min(i + 1, options.length - 1))breakcase 'ArrowUp':e.preventDefault()setActiveIndex(i => Math.max(i - 1, 0))breakcase 'Enter':e.preventDefault()onSelect(options[activeIndex])setIsOpen(false)breakcase 'Escape':setIsOpen(false)break}}return (<divrole="combobox"aria-expanded={isOpen}aria-haspopup="listbox"onKeyDown={handleKeyDown}>{/* 下拉菜单实现 */}</div>)}
焦点管理 (Focus Management)
typescript
export function Modal({ isOpen, onClose, children }: ModalProps) {const modalRef = useRef<HTMLDivElement>(null)const previousFocusRef = useRef<HTMLElement | null>(null)useEffect(() => {if (isOpen) {// 保存当前获取焦点的元素previousFocusRef.current = document.activeElement as HTMLElement// 将焦点移至弹窗modalRef.current?.focus()} else {// 关闭时恢复焦点previousFocusRef.current?.focus()}}, [isOpen])return isOpen ? (<divref={modalRef}role="dialog"aria-modal="true"tabIndex={-1}onKeyDown={e => e.key === 'Escape' && onClose()}>{children}</div>) : null}
记住:现代前端模式有助于构建可维护、高性能的用户界面。请根据项目的复杂度选择合适的模式。