---
name: frontend-patterns
description: Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.
---
# 前端開發模式
用於 React、Next.js 和高效能使用者介面的現代前端模式。
## 元件模式
### 組合優於繼承
```typescript
// ✅ 良好:元件組合
interface CardProps {
children: React.ReactNode
variant?: 'default' | 'outlined'
}
export function Card({ children, variant = 'default' }: CardProps) {
return
{children}
}
export function CardHeader({ children }: { children: React.ReactNode }) {
return {children}
}
export function CardBody({ children }: { children: React.ReactNode }) {
return {children}
}
// 使用方式
標題
內容
```
### 複合元件
```typescript
interface TabsContextValue {
activeTab: string
setActiveTab: (tab: string) => void
}
const TabsContext = createContext(undefined)
export function Tabs({ children, defaultTab }: {
children: React.ReactNode
defaultTab: string
}) {
const [activeTab, setActiveTab] = useState(defaultTab)
return (
{children}
)
}
export function TabList({ children }: { children: React.ReactNode }) {
return {children}
}
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 (
)
}
// 使用方式
概覽
詳情
```
### Render Props 模式
```typescript
interface DataLoaderProps {
url: string
children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode
}
export function DataLoader({ url, children }: DataLoaderProps) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [url])
return <>{children(data, loading, error)}>
}
// 使用方式
url="/api/markets">
{(markets, loading, error) => {
if (loading) return
if (error) return
return
}}
```
## 自訂 Hooks 模式
### 狀態管理 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
```typescript
interface UseQueryOptions {
onSuccess?: (data: T) => void
onError?: (error: Error) => void
enabled?: boolean
}
export function useQuery(
key: string,
fetcher: () => Promise,
options?: UseQueryOptions
) {
const [data, setData] = useState(null)
const [error, setError] = useState(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 Error
setError(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)
}
)
```
### Debounce Hook
```typescript
export function useDebounce(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(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])
```
## 狀態管理模式
### Context + Reducer 模式
```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
} | undefined>(undefined)
export function MarketProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, {
markets: [],
selectedMarket: null,
loading: false
})
return (
{children}
)
}
export function useMarkets() {
const context = useContext(MarketContext)
if (!context) throw new Error('useMarkets must be used within MarketProvider')
return context
}
```
## 效能優化
### 記憶化
```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(({ market }) => {
return (
{market.name}
{market.description}
)
})
```
### 程式碼分割與延遲載入
```typescript
import { lazy, Suspense } from 'react'
// ✅ 延遲載入重型元件
const HeavyChart = lazy(() => import('./HeavyChart'))
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
export function Dashboard() {
return (
}>
)
}
```
### 長列表虛擬化
```typescript
import { useVirtualizer } from '@tanstack/react-virtual'
export function VirtualMarketList({ markets }: { markets: Market[] }) {
const parentRef = useRef(null)
const virtualizer = useVirtualizer({
count: markets.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 100, // 預估行高
overscan: 5 // 額外渲染的項目數
})
return (
{virtualizer.getVirtualItems().map(virtualRow => (
))}
)
}
```
## 表單處理模式
### 帶驗證的受控表單
```typescript
interface FormData {
name: string
description: string
endDate: string
}
interface FormErrors {
name?: string
description?: string
endDate?: string
}
export function CreateMarketForm() {
const [formData, setFormData] = useState({
name: '',
description: '',
endDate: ''
})
const [errors, setErrors] = useState({})
const validate = (): boolean => {
const newErrors: FormErrors = {}
if (!formData.name.trim()) {
newErrors.name = '名稱為必填'
} else if (formData.name.length > 200) {
newErrors.name = '名稱必須少於 200 個字元'
}
if (!formData.description.trim()) {
newErrors.description = '描述為必填'
}
if (!formData.endDate) {
newErrors.endDate = '結束日期為必填'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validate()) return
try {
await createMarket(formData)
// 成功處理
} catch (error) {
// 錯誤處理
}
}
return (
)
}
```
## Error Boundary 模式
```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 (
發生錯誤
{this.state.error?.message}
)
}
return this.props.children
}
}
// 使用方式
```
## 動畫模式
### Framer Motion 動畫
```typescript
import { motion, AnimatePresence } from 'framer-motion'
// ✅ 列表動畫
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
return (
{markets.map(market => (
))}
)
}
// ✅ Modal 動畫
export function Modal({ isOpen, onClose, children }: ModalProps) {
return (
{isOpen && (
<>
{children}
>
)}
)
}
```
## 無障礙模式
### 鍵盤導航
```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 (
{/* 下拉選單實作 */}
)
}
```
### 焦點管理
```typescript
export function Modal({ isOpen, onClose, children }: ModalProps) {
const modalRef = useRef(null)
const previousFocusRef = useRef(null)
useEffect(() => {
if (isOpen) {
// 儲存目前聚焦的元素
previousFocusRef.current = document.activeElement as HTMLElement
// 聚焦 modal
modalRef.current?.focus()
} else {
// 關閉時恢復焦點
previousFocusRef.current?.focus()
}
}, [isOpen])
return isOpen ? (
e.key === 'Escape' && onClose()}
>
{children}
) : null
}
```
**記住**:現代前端模式能實現可維護、高效能的使用者介面。選擇符合你專案複雜度的模式。