Frontend Patterns

affaan-m/ECC/skills/frontend-patterns

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775无许可证275K 个星标收录于 2026年10月9日更新于 2026年10月9日仓库4天前更新

Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices. Use when building or reviewing React or Next.js components, state, or render performance.

AI 生成的概览

提供 React 与 Next.js 前端开发模式参考:组件、Hook、状态、性能、表单与无障碍。

功能
提供一份现代前端模式参考指南,面向 React 与 Next.js,涵盖组件组合、复合组件、render props、自定义 Hook,以及基于 Context 与 reducer 的状态管理。文档还介绍性能优化手段,如记忆化、代码分割与长列表虚拟化,并包含表单校验、错误边界、动画与无障碍模式。每种模式都配有 TypeScript 代码示例,便于代理在编写或审查界面代码时参考。
适用场景
适用于构建或审查 React、Next.js 组件、Hook、状态或数据获取时。也适合优化渲染性能、实现表单与校验,或为界面元素补充键盘操作与焦点管理无障碍能力。
运行要求
不含脚本或附带资源,仅为说明文档与代码示例。这些模式假定项目使用 React 或 Next.js 与 TypeScript,部分示例涉及可选库,如 Zustand、SWR、React Query、Zod、@tanstack/react-virtual 与 framer-motion。

Frontend Development Patterns

Modern frontend patterns for React, Next.js, and performant user interfaces.

When to Activate

  • Building React components (composition, props, rendering)
  • Managing state (useState, useReducer, Zustand, Context)
  • Implementing data fetching (SWR, React Query, server components)
  • Optimizing performance (memoization, virtualization, code splitting)
  • Working with forms (validation, controlled inputs, Zod schemas)
  • Handling client-side routing and navigation
  • Building accessible, responsive UI patterns

Component Patterns

Composition Over Inheritance

typescript
// PASS: GOOD: Component compositioninterface CardProps {  children: React.ReactNode  variant?: '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>}
// Usage<Card>  <CardHeader>Title</CardHeader>  <CardBody>Content</CardBody></Card>

Compound Components

typescript
interface TabsContextValue {  activeTab: string  setActiveTab: (tab: string) => void}
const TabsContext = createContext<TabsContextValue | undefined>(undefined)
export function Tabs({ children, defaultTab }: {  children: React.ReactNode  defaultTab: 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 (    <button      className={context.activeTab === id ? 'active' : ''}      onClick={() => context.setActiveTab(id)}    >      {children}    </button>  )}
// Usage<Tabs defaultTab="overview">  <TabList>    <Tab id="overview">Overview</Tab>    <Tab id="details">Details</Tab>  </TabList></Tabs>

Render Props Pattern

typescript
interface DataLoaderProps<T> {  url: string  children: (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)}</>}
// Usage<DataLoader<Market[]> url="/api/markets">  {(markets, loading, error) => {    if (loading) return <Spinner />    if (error) return <Error error={error} />    return <MarketList markets={markets!} />  }}</DataLoader>

Custom Hooks Patterns

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]}
// Usageconst [isOpen, toggleOpen] = useToggle()

Async Data Fetching Hook

typescript
interface UseQueryOptions<T> {  onSuccess?: (data: T) => void  onError?: (error: Error) => void  enabled?: 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)
  // Keep the latest fetcher/options in refs so refetch stays referentially  // stable even when callers pass inline functions and object literals.  // Without this, every render creates a new refetch, and the effect below  // re-runs after each state update - an infinite fetch loop.  const fetcherRef = useRef(fetcher)  const optionsRef = useRef(options)  useEffect(() => {    fetcherRef.current = fetcher    optionsRef.current = options  })
  const refetch = useCallback(async () => {    setLoading(true)    setError(null)
    try {      const result = await fetcherRef.current()      setData(result)      optionsRef.current?.onSuccess?.(result)    } catch (err) {      const error = err as Error      setError(error)      optionsRef.current?.onError?.(error)    } finally {      setLoading(false)    }  }, [])
  const enabled = options?.enabled !== false
  useEffect(() => {    if (enabled) {      refetch()    }  }, [key, enabled, refetch])
  return { data, error, loading, refetch }}
// Usageconst { 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)  })

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}
// Usageconst [searchQuery, setSearchQuery] = useState('')const debouncedQuery = useDebounce(searchQuery, 500)
useEffect(() => {  if (debouncedQuery) {    performSearch(debouncedQuery)  }}, [debouncedQuery])

State Management Patterns

Context + Reducer Pattern

typescript
interface State {  markets: Market[]  selectedMarket: Market | null  loading: 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: State  dispatch: 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
// PASS: useMemo for expensive computations// Copy before sorting - Array.prototype.sort mutates in placeconst sortedMarkets = useMemo(() => {  return [...markets].sort((a, b) => b.volume - a.volume)}, [markets])
// PASS: useCallback for functions passed to childrenconst handleSearch = useCallback((query: string) => {  setSearchQuery(query)}, [])
// PASS: React.memo for pure componentsexport 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'
// PASS: Lazy load heavy componentsconst 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,  // Estimated row height    overscan: 5  // Extra items to render  })
  return (    <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>      <div        style={{          height: `${virtualizer.getTotalSize()}px`,          position: 'relative'        }}      >        {virtualizer.getVirtualItems().map(virtualRow => (          <div            key={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: string  description: string  endDate: string}
interface FormErrors {  name?: string  description?: string  endDate?: 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()) return
    try {      await createMarket(formData)      // Success handling    } catch (error) {      // Error handling    }  }
  return (    <form onSubmit={handleSubmit}>      <input        value={formData.name}        onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}        placeholder="Market name"      />      {errors.name && <span className="error">{errors.name}</span>}
      {/* Other fields */}
      <button type="submit">Create Market</button>    </form>  )}

Error Boundary Pattern

typescript
interface ErrorBoundaryState {  hasError: boolean  error: 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  }}
// Usage<ErrorBoundary>  <App /></ErrorBoundary>

Animation Patterns

Framer Motion Animations

typescript
import { motion, AnimatePresence } from 'framer-motion'
// PASS: List animationsexport function AnimatedMarketList({ markets }: { markets: Market[] }) {  return (    <AnimatePresence>      {markets.map(market => (        <motion.div          key={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>  )}
// PASS: Modal animationsexport function Modal({ isOpen, onClose, children }: ModalProps) {  return (    <AnimatePresence>      {isOpen && (        <>          <motion.div            className="modal-overlay"            initial={{ opacity: 0 }}            animate={{ opacity: 1 }}            exit={{ opacity: 0 }}            onClick={onClose}          />          <motion.div            className="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))        break      case 'ArrowUp':        e.preventDefault()        setActiveIndex(i => Math.max(i - 1, 0))        break      case 'Enter':        e.preventDefault()        onSelect(options[activeIndex])        setIsOpen(false)        break      case 'Escape':        setIsOpen(false)        break    }  }
  return (    <div      role="combobox"      aria-expanded={isOpen}      aria-haspopup="listbox"      onKeyDown={handleKeyDown}    >      {/* Dropdown implementation */}    </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) {      // Save currently focused element      previousFocusRef.current = document.activeElement as HTMLElement
      // Focus modal      modalRef.current?.focus()    } else {      // Restore focus when closing      previousFocusRef.current?.focus()    }  }, [isOpen])
  return isOpen ? (    <div      ref={modalRef}      role="dialog"      aria-modal="true"      tabIndex={-1}      onKeyDown={e => e.key === 'Escape' && onClose()}    >      {children}    </div>  ) : null}

Remember: Modern frontend patterns enable maintainable, performant user interfaces. Choose patterns that fit your project complexity.

来源与署名

来源:affaan-m/ECC位于skills/frontend-patterns提交ef648e0

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架