Frontend Architecture Patterns
Оптимизируйте рабочий процесс с фронтендом с помощью Claude Code. Внедряйте профессиональные паттерны React и Next.js для управления состоянием, производительности и композиции UI.
Frontend Architecture Patterns предоставляет Claude обширную библиотеку современных стратегий разработки на React и Next.js. Он помогает ИИ реализовывать продвинутую композицию компонентов, кастомные хуки для управления состоянием и загрузки данных, оптимизацию производительности с помощью мемоизации и виртуализации, а также надежную работу с формами и валидацией. Этот навык необходим разработчикам масштабируемых веб-приложений, которые хотят, чтобы их кодовая база следовала лучшим отраслевым практикам поддерживаемости, типобезопасности и оптимального пользовательского опыта.
Ключевые особенности
Варианты использования
| name | frontend-patterns |
|---|---|
| description | React, Next.js, state yönetimi, performans optimizasyonu ve UI en iyi uygulamaları için frontend geliştirme kalıpları. |
| origin | ECC |
React, Next.js ve performanslı kullanıcı arayüzleri için modern frontend kalıpları.
- React bileşenleri oluştururken (composition, props, rendering)
- State yönetirken (useState, useReducer, Zustand, Context)
- Veri çekme implementasyonu (SWR, React Query, server components)
- Performans optimize ederken (memoization, virtualization, code splitting)
- Formlarla çalışırken (validation, controlled inputs, Zod schemas)
- Client-side routing ve navigasyon işlerken
- Erişilebilir, responsive UI kalıpları oluştururken
// PASS: İYİ: Bileşen composition
interface 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>
}
// Kullanım
<Card>
<CardHeader>Başlık</CardHeader>
<CardBody>İçerik</CardBody>
</Card>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>
)
}
// Kullanım
<Tabs defaultTab="overview">
<TabList>
<Tab id="overview">Genel Bakış</Tab>
<Tab id="details">Detaylar</Tab>
</TabList>
</Tabs>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)}</>
}
// Kullanım
<DataLoader<Market[]> url="/api/markets">
{(markets, loading, error) => {
if (loading) return <Spinner />
if (error) return <Error error={error} />
return <MarketList markets={markets!} />
}}
</DataLoader>export function useToggle(initialValue = false): [boolean, () => void] {
const [value, setValue] = useState(initialValue)
const toggle = useCallback(() => {
setValue(v => !v)
}, [])
return [value, toggle]
}
// Kullanım
const [isOpen, toggleOpen] = useToggle()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)
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 }
}
// Kullanım
const { data: markets, loading, error, refetch } = useQuery(
'markets',
() => fetch('/api/markets').then(r => r.json()),
{
onSuccess: data => console.log('Getirilen', data.length, 'market'),
onError: err => console.error('Başarısız:', err)
}
)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
}
// Kullanım
const [searchQuery, setSearchQuery] = useState('')
const debouncedQuery = useDebounce(searchQuery, 500)
useEffect(() => {
if (debouncedQuery) {
performSearch(debouncedQuery)
}
}, [debouncedQuery])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
}// PASS: Pahalı hesaplamalar için useMemo
const sortedMarkets = useMemo(() => {
return markets.sort((a, b) => b.volume - a.volume)
}, [markets])
// PASS: Alt bileşenlere geçirilen fonksiyonlar için useCallback
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
}, [])
// PASS: Pure bileşenler için React.memo
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
return (
<div className="market-card">
<h3>{market.name}</h3>
<p>{market.description}</p>
</div>
)
})import { lazy, Suspense } from 'react'
// PASS: Ağır bileşenleri lazy yükle
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>
)
}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, // Tahmini satır yüksekliği
overscan: 5 // Ekstra render edilecek öğeler
})
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>
)
}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 = 'İsim gereklidir'
} else if (formData.name.length > 200) {
newErrors.name = 'İsim 200 karakterden az olmalıdır'
}
if (!formData.description.trim()) {
newErrors.description = 'Açıklama gereklidir'
}
if (!formData.endDate) {
newErrors.endDate = 'Bitiş tarihi gereklidir'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validate()) return
try {
await createMarket(formData)
// Başarı işleme
} catch (error) {
// Hata işleme
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}
placeholder="Market ismi"
/>
{errors.name && <span className="error">{errors.name}</span>}
{/* Diğer alanlar */}
<button type="submit">Market Oluştur</button>
</form>
)
}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>Bir şeyler yanlış gitti</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false })}>
Tekrar dene
</button>
</div>
)
}
return this.props.children
}
}
// Kullanım
<ErrorBoundary>
<App />
</ErrorBoundary>import { motion, AnimatePresence } from 'framer-motion'
// PASS: Liste animasyonları
export 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 animasyonları
export 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>
)
}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 implementasyonu */}
</div>
)
}export function Modal({ isOpen, onClose, children }: ModalProps) {
const modalRef = useRef<HTMLDivElement>(null)
const previousFocusRef = useRef<HTMLElement | null>(null)
useEffect(() => {
if (isOpen) {
// Şu anki focus'lanmış elementi kaydet
previousFocusRef.current = document.activeElement as HTMLElement
// Modal'a focus yap
modalRef.current?.focus()
} else {
// Kapatırken focus'u geri yükle
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
}Unutmayın: Modern frontend kalıpları sürdürülebilir, performanslı kullanıcı arayüzleri sağlar. Proje karmaşıklığınıza uyan kalıpları seçin.
🌐 Что это
Этот навык — справочник-шпаргалка по современным паттернам фронтенд-разработки на React и Next.js. Он не учит синтаксису, а показывает проверенные приёмы: как строить компоненты, управлять состоянием, оптимизировать производительность и делать UI доступным и удобным. Навык будет полезен как начинающим разработчикам, которые хотят писать чистый и поддерживаемый код, так и опытным — для быстрой сверки с лучшими практиками.
⚙️ Как работает
Набор разделён на ключевые темы. В каждой — краткое описание паттерна, пример кода (в исходном plain_text) и сценарий использования. Ниже — основные блоки.
🧩 Композиция вместо наследования
Основной принцип: строить UI из маленьких, переиспользуемых компонентов, комбинируя их через children. Вместо одного гигантского Card с кучей пропсов вы создаёте Card, CardHeader и CardBody. Это делает код более гибким.
interface CardProps {
children: React.ReactNode;
variant?: 'default' | 'outlined';
}
export function Card({ children, variant = 'default' }: CardProps) {
return <div className={`card card-${variant}`}>{children};
}
📦 Compound Components (Составные компоненты)
Паттерн, когда группа компонентов (например, Tabs, TabList, Tab) работает как единое целое, разделяя неявное состояние через React Context. Родительский Tabs хранит активную вкладку и предоставляет метод её переключения через контекст — детям не нужно получать это через пропсы.
<Tabs defaultTab="overview">
`TabList`
<Tab id="overview">Обзор<Tab id="details">Детали```
### 🔄 Render Props и кастомные хуки
**Render Props** — когда компонент получает не статический JSX, а функцию, которая возвращает JSX в зависимости от внутреннего состояния (например, `DataLoader` отображает разные UI в статусах `loading`, `error`, `success`).
Кастомные хуки — более современный и чистый способ. Навык приводит примеры:
- `useToggle` — простой бинарный переключатель.
- `useQuery` — собственная обёртка для асинхронной загрузки данных с колбэками `onSuccess`, `onError` и флагом `enabled`.
- `useDebounce` — задержка обновления значения (полезно для поиска при вводе).
### 🗃️ Управление состоянием: Context + Reducer
Для сложного состояния, которое нужно разделять между множеством компонентов, рекомендуется связка `useReducer` + `Context`. Паттерн описывает типизированный `reducer` (с Union-типами для экшенов) и провайдер, а также кастомный хук `useMarkets` для доступа к контексту с проверкой на ошибки.
### ⚡ Оптимизация производительности
Навык перечисляет четыре основных подхода:
1. **Мемоизация** — `useMemo` для тяжёлых вычислений, `useCallback` для функций-пропсов, `React.memo` для компонентов, которые не должны перерисовываться без изменения пропсов.
2. **Lazy Loading и Code Splitting** — динамический импорт тяжёлых компонентов (`lazy` + `Suspense`) для уменьшения размера начального бандла.
3. **Виртуализация списков** — библиотека `@tanstack/react-virtual` рендерит только видимые элементы в длинном списке, экономя DOM-узлы (с параметрами `overscan` для плавности прокрутки).
4. **Оптимизация форм** — контролируемые инпуты с валидацией через отдельное состояние `errors` и единую функцию `validate`.
### 🧪 Error Boundary (Граница ошибок)
Классовый компонент-обёртка, который ловит ошибки рендера в дочерних компонентах и показывает запасной UI (fallback) вместо того, чтобы ронять всё приложение. Включает кнопку «Попробовать снова», которая сбрасывает состояние ошибки.
### 🎮 Доступность (A11y)
Паттерны для клавиатурной навигации: обработка `ArrowDown`/`ArrowUp` для выпадающих списков (с ролью `combobox` и `aria-expanded`), `Enter` для выбора, `Escape` для закрытия. Отдельный пример — управление фокусом в модалках: сохраняется предыдущий элемент (`previousFocusRef`) и фокус возвращается при закрытии.
### ✨ Анимации (Framer Motion)
Паттерны для плавных появлений/исчезновений элементов списка и модальных окон через `AnimatePresence` и `motion.div`. Начальное (`initial`), активное (`animate`) и конечное (`exit`) состояния задаются через объекты с `opacity`, `y` и `scale`.
## 🎯 Когда использовать
- Когда вам нужно **единообразно** организовать React-компоненты в новой или существующей кодовой базе.
- Для **рефакторинга**: если видите «божественные» компоненты с десятками пропсов — разбейте их на составные (Compound Components) или используйте композицию.
- При **оптимизации** производительности: перед добавлением `useMemo` или `lazy` сверьтесь с примерами из навыка.
- В проектах, где важна **доступность**: заимствуйте готовые обработчики клавиатуры и управления фокусом.
- При **работе с формами**: возьмите за основу паттерн с `useState` для `FormErrors` и общей функцией валидации.
## ⚠️ Важно знать
- Код в навыке — **иллюстративный**, это не готовая библиотека. Типы (`Market`, `Market[]`) — плейсхолдеры; замените их на свои.
- Для асинхронной загрузки данных стоит рассмотреть готовые библиотеки (React Query, SWR), но `useQuery` из навыка показывает общую механику.
- `ErrorBoundary` — классовый компонент, потому что React пока не предоставляет хуков для `componentDidCatch`. Это единственный случай, когда класс оправдан.
- Виртуализация с `@tanstack/react-virtual` — отличное решение для таблиц и длинных списков, но требует правильной настройки `estimateSize` и `overscan`.
- Анимации с `framer-motion` увеличивают размер бандла. Для простых переходов можно обойтись CSS-переходами.
> **Совет:** не применяйте все паттерны сразу. Выбирайте те, которые решают конкретную проблему в вашем проекте, и комбинируйте их осмысленно.
Подходит ли этот скилл для новичков?
Хотя в нем реализованы продвинутые паттерны, он служит отличным руководством для разработчиков, желающих внедрить профессиональные отраслевые стандарты в свои проекты.
Какие фреймворки поддерживаются этим скиллом?
Этот скилл специально ориентирован на React и Next.js, предоставляя паттерны как для клиентских, так и для серверных компонентов.
Как этот скилл улучшает производительность приложения?
Он содержит логику реализации мемоизации (useMemo/useCallback), ленивой загрузки с Suspense и виртуализации списков с помощью TanStack Virtual.
Может ли этот скилл помочь с обработкой форм?
Да, он включает паттерны для контролируемых форм, валидации на основе состояния и обработки ошибок для обеспечения надежного управления пользовательским вводом.
Синхронизируйте навыки с Claude Cowork, Claude Code, Codex и другими.
Установка одной командой.
npx skillfish add affaan-m/everything-claude-code frontend-patternsИсточник: https://mcpmarket.com/tools/skills/frontend-architecture-patterns-6
Комментарии
Комментариев пока нет. Будьте первым.