← 返回 Skills 市场
npjameszheng1125-netizen

Frontend Patterns

作者 npjameszheng1125-netizen · GitHub ↗ · v1.0.0 · MIT-0
cross-platform ✓ 安全检测通过
265
总下载
0
收藏
8
当前安装
1
版本数
在 OpenClaw 中安装
/install frontend-patterns
功能描述
Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.
使用说明 (SKILL.md)

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

// ✅ GOOD: Component composition
interface CardProps {
  children: React.ReactNode
  variant?: 'default' | 'outlined'
}

export function Card({ children, variant = 'default' }: CardProps) {
  return \x3Cdiv className={`card card-${variant}`}>{children}\x3C/div>
}

export function CardHeader({ children }: { children: React.ReactNode }) {
  return \x3Cdiv className="card-header">{children}\x3C/div>
}

export function CardBody({ children }: { children: React.ReactNode }) {
  return \x3Cdiv className="card-body">{children}\x3C/div>
}

// Usage
\x3CCard>
  \x3CCardHeader>Title\x3C/CardHeader>
  \x3CCardBody>Content\x3C/CardBody>
\x3C/Card>

Compound Components

interface TabsContextValue {
  activeTab: string
  setActiveTab: (tab: string) => void
}

const TabsContext = createContext\x3CTabsContextValue | undefined>(undefined)

export function Tabs({ children, defaultTab }: {
  children: React.ReactNode
  defaultTab: string
}) {
  const [activeTab, setActiveTab] = useState(defaultTab)

  return (
    \x3CTabsContext.Provider value={{ activeTab, setActiveTab }}>
      {children}
    \x3C/TabsContext.Provider>
  )
}

export function TabList({ children }: { children: React.ReactNode }) {
  return \x3Cdiv className="tab-list">{children}\x3C/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 (
    \x3Cbutton
      className={context.activeTab === id ? 'active' : ''}
      onClick={() => context.setActiveTab(id)}
    >
      {children}
    \x3C/button>
  )
}

// Usage
\x3CTabs defaultTab="overview">
  \x3CTabList>
    \x3CTab id="overview">Overview\x3C/Tab>
    \x3CTab id="details">Details\x3C/Tab>
  \x3C/TabList>
\x3C/Tabs>

Render Props Pattern

interface DataLoaderProps\x3CT> {
  url: string
  children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode
}

export function DataLoader\x3CT>({ url, children }: DataLoaderProps\x3CT>) {
  const [data, setData] = useState\x3CT | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState\x3CError | null>(null)

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false))
  }, [url])

  return \x3C>{children(data, loading, error)}\x3C/>
}

// Usage
\x3CDataLoader\x3CMarket[]> url="/api/markets">
  {(markets, loading, error) => {
    if (loading) return \x3CSpinner />
    if (error) return \x3CError error={error} />
    return \x3CMarketList markets={markets!} />
  }}
\x3C/DataLoader>

Custom Hooks Patterns

State Management Hook

export function useToggle(initialValue = false): [boolean, () => void] {
  const [value, setValue] = useState(initialValue)

  const toggle = useCallback(() => {
    setValue(v => !v)
  }, [])

  return [value, toggle]
}

// Usage
const [isOpen, toggleOpen] = useToggle()

Async Data Fetching Hook

interface UseQueryOptions\x3CT> {
  onSuccess?: (data: T) => void
  onError?: (error: Error) => void
  enabled?: boolean
}

export function useQuery\x3CT>(
  key: string,
  fetcher: () => Promise\x3CT>,
  options?: UseQueryOptions\x3CT>
) {
  const [data, setData] = useState\x3CT | null>(null)
  const [error, setError] = useState\x3CError | 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 }
}

// Usage
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

export function useDebounce\x3CT>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState\x3CT>(value)

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value)
    }, delay)

    return () => clearTimeout(handler)
  }, [value, delay])

  return debouncedValue
}

// Usage
const [searchQuery, setSearchQuery] = useState('')
const debouncedQuery = useDebounce(searchQuery, 500)

useEffect(() => {
  if (debouncedQuery) {
    performSearch(debouncedQuery)
  }
}, [debouncedQuery])

State Management Patterns

Context + Reducer Pattern

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\x3C{
  state: State
  dispatch: Dispatch\x3CAction>
} | undefined>(undefined)

export function MarketProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(reducer, {
    markets: [],
    selectedMarket: null,
    loading: false
  })

  return (
    \x3CMarketContext.Provider value={{ state, dispatch }}>
      {children}
    \x3C/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

// ✅ useMemo for expensive computations
const sortedMarkets = useMemo(() => {
  return markets.sort((a, b) => b.volume - a.volume)
}, [markets])

// ✅ useCallback for functions passed to children
const handleSearch = useCallback((query: string) => {
  setSearchQuery(query)
}, [])

// ✅ React.memo for pure components
export const MarketCard = React.memo\x3CMarketCardProps>(({ market }) => {
  return (
    \x3Cdiv className="market-card">
      \x3Ch3>{market.name}\x3C/h3>
      \x3Cp>{market.description}\x3C/p>
    \x3C/div>
  )
})

Code Splitting & Lazy Loading

import { lazy, Suspense } from 'react'

// ✅ Lazy load heavy components
const HeavyChart = lazy(() => import('./HeavyChart'))
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))

export function Dashboard() {
  return (
    \x3Cdiv>
      \x3CSuspense fallback={\x3CChartSkeleton />}>
        \x3CHeavyChart data={data} />
      \x3C/Suspense>

      \x3CSuspense fallback={null}>
        \x3CThreeJsBackground />
      \x3C/Suspense>
    \x3C/div>
  )
}

Virtualization for Long Lists

import { useVirtualizer } from '@tanstack/react-virtual'

export function VirtualMarketList({ markets }: { markets: Market[] }) {
  const parentRef = useRef\x3CHTMLDivElement>(null)

  const virtualizer = useVirtualizer({
    count: markets.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 100,  // Estimated row height
    overscan: 5  // Extra items to render
  })

  return (
    \x3Cdiv ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
      \x3Cdiv
        style={{
          height: `${virtualizer.getTotalSize()}px`,
          position: 'relative'
        }}
      >
        {virtualizer.getVirtualItems().map(virtualRow => (
          \x3Cdiv
            key={virtualRow.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualRow.size}px`,
              transform: `translateY(${virtualRow.start}px)`
            }}
          >
            \x3CMarketCard market={markets[virtualRow.index]} />
          \x3C/div>
        ))}
      \x3C/div>
    \x3C/div>
  )
}

Form Handling Patterns

Controlled Form with Validation

interface FormData {
  name: string
  description: string
  endDate: string
}

interface FormErrors {
  name?: string
  description?: string
  endDate?: string
}

export function CreateMarketForm() {
  const [formData, setFormData] = useState\x3CFormData>({
    name: '',
    description: '',
    endDate: ''
  })

  const [errors, setErrors] = useState\x3CFormErrors>({})

  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 (
    \x3Cform onSubmit={handleSubmit}>
      \x3Cinput
        value={formData.name}
        onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}
        placeholder="Market name"
      />
      {errors.name && \x3Cspan className="error">{errors.name}\x3C/span>}

      {/* Other fields */}

      \x3Cbutton type="submit">Create Market\x3C/button>
    \x3C/form>
  )
}

Error Boundary Pattern

interface ErrorBoundaryState {
  hasError: boolean
  error: Error | null
}

export class ErrorBoundary extends React.Component\x3C
  { 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 (
        \x3Cdiv className="error-fallback">
          \x3Ch2>Something went wrong\x3C/h2>
          \x3Cp>{this.state.error?.message}\x3C/p>
          \x3Cbutton onClick={() => this.setState({ hasError: false })}>
            Try again
          \x3C/button>
        \x3C/div>
      )
    }

    return this.props.children
  }
}

// Usage
\x3CErrorBoundary>
  \x3CApp />
\x3C/ErrorBoundary>

Animation Patterns

Framer Motion Animations

import { motion, AnimatePresence } from 'framer-motion'

// ✅ List animations
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
  return (
    \x3CAnimatePresence>
      {markets.map(market => (
        \x3Cmotion.div
          key={market.id}
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -20 }}
          transition={{ duration: 0.3 }}
        >
          \x3CMarketCard market={market} />
        \x3C/motion.div>
      ))}
    \x3C/AnimatePresence>
  )
}

// ✅ Modal animations
export function Modal({ isOpen, onClose, children }: ModalProps) {
  return (
    \x3CAnimatePresence>
      {isOpen && (
        \x3C>
          \x3Cmotion.div
            className="modal-overlay"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            onClick={onClose}
          />
          \x3Cmotion.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}
          \x3C/motion.div>
        \x3C/>
      )}
    \x3C/AnimatePresence>
  )
}

Accessibility Patterns

Keyboard Navigation

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 (
    \x3Cdiv
      role="combobox"
      aria-expanded={isOpen}
      aria-haspopup="listbox"
      onKeyDown={handleKeyDown}
    >
      {/* Dropdown implementation */}
    \x3C/div>
  )
}

Focus Management

export function Modal({ isOpen, onClose, children }: ModalProps) {
  const modalRef = useRef\x3CHTMLDivElement>(null)
  const previousFocusRef = useRef\x3CHTMLElement | 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 ? (
    \x3Cdiv
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      tabIndex={-1}
      onKeyDown={e => e.key === 'Escape' && onClose()}
    >
      {children}
    \x3C/div>
  ) : null
}

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

安全使用建议
This skill is a read-only reference of React/Next.js patterns and appears coherent and low-risk: it doesn't ask for credentials, doesn't install software, and contains only code examples. Two practical cautions: (1) the skill's source/homepage is unknown — if provenance matters for compliance or licensing, prefer content from known maintainers or include a license/author check before reuse; (2) the agent is allowed to invoke this skill implicitly (default) — if you want to limit automated use, disable implicit invocation in agent settings. Otherwise this skill is safe to install from a scope standpoint.
功能分析
Type: OpenClaw Skill Name: frontend-patterns Version: 1.0.0 The skill bundle contains standard documentation and code examples for React and Next.js development patterns, such as component composition, custom hooks, and accessibility. There is no evidence of malicious intent, data exfiltration, or prompt injection across SKILL.md or the configuration files.
能力评估
Purpose & Capability
The name and description (frontend React/Next.js patterns) match the SKILL.md contents: component patterns, hooks, state management, performance, and accessibility examples. There are no unexplained dependencies, binaries, or credentials declared.
Instruction Scope
SKILL.md provides code examples and guidance for frontend development and does not instruct the agent to read system files, environment variables, credentials, or to transmit data to external endpoints. Instructions are scoped to coding patterns and usage examples.
Install Mechanism
No install specification or code files that would write to disk or fetch remote artifacts. Being instruction-only, it has minimal installation risk.
Credentials
The skill declares no required environment variables, credentials, or config paths. There is no disproportionate request for sensitive data relative to the stated purpose.
Persistence & Privilege
always:false (normal). agents/openai.yaml sets allow_implicit_invocation: true which permits the agent to invoke this guidance automatically where relevant — this is expected for helper/reference skills and is not a direct security concern given no credentials or install steps. If you prefer manual control, consider disabling implicit invocation at the agent/platform level.
如何使用
  1. 确保已安装 OpenClaw(本地或 Docker 部署)
  2. 在对话框中输入安装命令:/install frontend-patterns
  3. 安装完成后,直接呼叫该 Skill 的名称或使用 /frontend-patterns 触发
  4. 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
版本历史
v1.0.0
Initial release of the "frontend-patterns" skill. - Provides modern frontend patterns for React and Next.js. - Includes examples for component architecture (composition, compound components, render props). - Offers custom hooks templates for toggling, debouncing, and async queries. - Demonstrates state management patterns with Context and Reducer. - Covers performance optimizations: memoization, code splitting, and virtualization. - Highlights UI best practices, data fetching strategies, and accessible component structures.
元数据
Slug frontend-patterns
版本 1.0.0
许可证 MIT-0
累计安装 8
当前安装数 8
历史版本数 1
常见问题

Frontend Patterns 是什么?

Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices. 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 265 次。

如何安装 Frontend Patterns?

在 OpenClaw 或 Claude Code 对话框中运行命令「/install frontend-patterns」即可一键安装,无需额外配置。

Frontend Patterns 是免费的吗?

是的,Frontend Patterns 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。

Frontend Patterns 支持哪些平台?

Frontend Patterns 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。

谁开发了 Frontend Patterns?

由 npjameszheng1125-netizen(@npjameszheng1125-netizen)开发并维护,当前版本 v1.0.0。

💬 留言讨论