Agentic Awesome Skills 前端模式实战指南:从组件组合到性能优化的 React 开发范式
【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,445+ agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/gh_mirrors/an/agentic-awesome-skills
本文以 AAS(Agentic Awesome Skills)仓库内置的 前端模式技能详细指南 为骨架,系统梳理 React / Next.js 场景下的组件组合、自定义 Hooks、状态管理、性能优化、表单校验、错误边界、动画与可访问性八大类模式,并对照仓库自身的 Web 工作台前端 源码给出可验证的实现参照。读完本文,你将掌握一套可直接落地的现代前端开发范式,并能在自己的项目里按复杂度选择合适的模式组合。
技能背景与文档定位
cc-skill-frontend-patterns是 AAS 仓库中面向 React、Next.js 与高性能 UI 构建的前端开发技能,其入口定义见 SKILL.md,本指南 detailed-guide.md 是它的完整程序与参考材料,供 Agent 聚焦加载或端到端执行时通读。
技能元信息(来自 SKILL.md frontmatter)明确其适用范围与边界:
| 元信息字段 | 值 |
|---|---|
name | cc-skill-frontend-patterns |
description | React、Next.js、状态管理、性能优化与 UI 最佳实践 |
risk | critical(高风险,需谨慎触发) |
source | community(社区来源) |
date_added | 2026-02-27 |
该技能的安全约束同样适用:仅在任务与上述范围明确匹配时使用;输出不能替代环境级验证、测试或专家评审;当输入、权限、安全边界或成功标准缺失时应停下提问。这意味着文中的每个模式都应结合你的实际项目做适配验证,而非机械照搬。
仓库的 Web 应用(AAS Core Workbench)本身就是这些模式的活体示例:它用 React + Vite + TypeScript 构建,包含 Context 全局状态、自定义 Hooks、受控表单、memo 化组件与懒加载路由,下文会逐一对位引用。
组件模式:组合优于继承
Composition Over Inheritance(组合优于继承)
指南开篇强调用组件组合代替类继承来复用 UI 结构。其核心思路是:让父组件负责布局与语义,子组件负责各自的区块,通过children插槽自由拼装:
// ✅ GOOD: Component 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> } // Usage <Card> <CardHeader>Title</CardHeader> <CardBody>Content</CardBody> </Card>这种模式的要点:
- 用
variant?: 'default' | 'outlined'之类的联合类型约束可选项,替代繁琐的布尔 props 组合; - 每个子组件只负责自己的
children渲染,职责单一,便于测试与替换; - 变更 UI 结构(如把
CardHeader换掉)不需要改动Card本体。
AAS 仓库的 SkillCard.tsx 就是组合模式的实证:它把整行技能项拆成Link、标题行、描述、元信息(Risk、date_added)、操作区(短名单按钮与星标按钮)等多个独立区块组合进一个article.skill-row,同时通过可选 props(shortlisted、onToggleShortlist)对外暴露扩展点,而不是用一层层继承去派生子类。
Compound Components(复合组件)
复合组件模式把「容器 + 子部件」封装为一个隐式共享状态的 API:外层组件通过 Context 下发状态,内层部件通过useContext消费,无需逐层 prop drilling。
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>两个关键设计点:
- Context 值可为 undefined:
createContext<TabsContextValue | undefined>(undefined),并在消费端if (!context) throw new Error('Tab must be used within Tabs')。这保证了子部件一旦被放到错误的容器外就会快速失败(fail-fast),而不是静默拿到 undefined; - 容器持有单一状态源:
activeTab只存在于Tabs,所有Tab都是无状态的受控展示件。
AAS 的 SkillContext.tsx 使用了完全相同的「Provider + 消费端守卫」手法:SkillProvider持有 skills/stars/loading/error 状态,useSkills()在 context 为 undefined 时抛出'useSkills must be used within a SkillProvider',并在 App.tsx 顶层用<CatalogRouteProvider>包裹路由以全局共享目录数据。
Render Props Pattern(渲染属性模式)
Render Props 把「数据获取逻辑」与「渲染逻辑」解耦:组件负责取数,把data / loading / error通过函数参数交还给调用方决定如何展示。
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>泛型DataLoader<T>让一个组件服务任意数据类型;children函数签名(data | null, loading, error)把三态渲染完全交给调用方,组件自身不关心具体 UI。这样取数逻辑可以被多页面复用,而展示层保持各自的差异化。
自定义 Hooks 模式
状态管理 Hook(useToggle)
把高频的状态切换逻辑收敛成一个返回值稳定的元组,消费方语义清晰:
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()注意setValue(v => !v)使用函数式更新,避免闭包读到过期值;useCallback保证toggle引用稳定,可安全放进子组件的 effect 依赖。
AAS 的 useSkillShortlist.ts 是更复杂的状态 Hook 实证:它把浏览器localStorage读写、跨标签页同步(storage事件)、自定义变更事件(aas-skill-shortlist-change)、持久化失败回滚全部封装进一个 Hook,对外只暴露{ ids, toggle, clear }。其注释还解释了为什么在 effect 中写存储而非在setIdsupdater 里写——React StrictMode 会双调用 updater,副作用放那里会执行两次。
异步数据获取 Hook(useQuery)
一个带防重复请求、可手动重取、支持成功/失败回调的可复用取数 Hook:
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 } } // 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) } )要点:
enabled: false可延迟首次请求(如等待表单就绪后再拉数据);key参与 effect 依赖,key 变化自动重新取数;refetch暴露给调用方做手动刷新(如按钮点击);finally保证 loading 一定复位,避免按钮永久禁用。
AAS 的 SkillContext.tsx 在生产代码里做了同样的增强:用latestRequestRef记录请求序号,响应返回时若序号已过期则丢弃,从根源上杜绝竞态(快速刷新时旧响应覆盖新数据)。它还实现了多候选 URL 的级联取数——依次尝试多个skills_index.json来源,全部失败才抛错,这正是useQuery在企业场景下的进阶演化。
Debounce Hook(防抖 Hook)
搜索框等高频输入场景下,用防抖把「值变化」与「副作用触发」解耦:
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 } // Usage const [searchQuery, setSearchQuery] = useState('') const debouncedQuery = useDebounce(searchQuery, 500) useEffect(() => { if (debouncedQuery) { performSearch(debouncedQuery) } }, [debouncedQuery])其原理是:每次value变化都会先clearTimeout再重新setTimeout,只有停顿超过delay(示例 500ms)后才提交新值。effect 返回的清理函数是防抖正确性的关键——组件卸载或依赖变化时旧定时器必须被清除,否则会出现「已卸载组件上 setState」的告警与无效请求。AAS 的 catalogSearch.ts 配套测试也覆盖了类似的高频输入场景,验证了防抖在真实搜索中的必要性。
状态管理模式:Context + Reducer
当跨层级共享的状态开始变得复杂(多个字段、多个更新动作),用useReducer收敛更新逻辑,用 Context 广播状态与 dispatch:
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 }模式价值:
- Action 是唯一变更入口:
type Action = { type: 'SET_MARKETS' } | ...用可辨识联合(discriminated union)穷举所有动作,reducer 的switch无法匹配到未定义的动作; - 不可变更新:每个 case 都返回新对象(
{ ...state, ... }),便于调试与时间旅行; - 公开一个自定义 Hook:
useMarkets()屏蔽 Context 细节,且内置「必须在 Provider 内使用」的守卫,防止误用。
AAS 在 SkillContext.tsx 中实践了同一套路:虽然状态较多(skills、stars、loading、error、refreshSkills),但通过useMemo缓存 context value(依赖数组为[skills, stars, loading, error, refreshSkills]),避免 Provider 每次渲染都生成新对象导致所有消费者重渲染——这是 Context + Reducer 模式最容易踩的性能坑,务必牢记。
性能优化模式
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<MarketCardProps>(({ market }) => { return ( <div className="market-card"> <h3>{market.name}</h3> <p>{market.description}</p> </div> ) })useMemo:仅在markets引用变化时重算排序,避免每次渲染都执行 O(n log n) 排序;useCallback:保证传给子组件的回调引用稳定,让React.memo的浅比较真正生效;React.memo:props 未变时跳过重渲染,适合纯展示组件。
AAS 的 SkillCard.tsx 用React.memo包裹了整个技能卡片,并设置SkillCard.displayName = 'SkillCard'便于 React DevTools 调试——这是团队协作时容易被忽略、但值得养成的习惯。仓库还配有 App.performance.test.tsx 对应用做性能回归验证。
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 ( <div> <Suspense fallback={<ChartSkeleton />}> <HeavyChart data={data} /> </Suspense> <Suspense fallback={null}> <ThreeJsBackground /> </Suspense> </div> ) }两个细节值得学习:一是每个懒加载组件各自拥有独立的Suspense边界,避免一个组件加载失败拖垮整页;二是装饰性组件(如 3D 背景)用fallback={null},不阻塞内容渲染。
AAS 的 App.tsx 在生产中把这套模式用到了极致:所有页面级组件(Landing、Home、SkillDetail、Workbench、Plugins、TopicLanding、NotFound)全部lazy(() => import(...)),由路由层统包在<Suspense fallback={<div>Loading...</div>}>内,实现整站按路由分包加载。
Virtualization for Long Lists(长列表虚拟化)
列表项数量上千时,DOM 节点数会拖垮渲染与滚动。虚拟化只渲染视口附近的行:
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> ) }实现原理与参数语义:
getTotalSize()撑起滚动条总高度(count × estimateSize);- 每个可见行
position: absolute+translateY(start)定位,滚动时复用已有节点; estimateSize: 100是行高估算值,用于计算首屏需渲染的行数;overscan: 5控制在视口上下额外预渲染 5 行,避免快速滚动出现白屏。
AAS 目录页同样面对数千条技能记录的渲染压力,其 App.performance.test.tsx 与列表组件的构建方式体现了「先测量再优化」的思路:不要过早虚拟化,当列表真正出现卡顿时再引入。
表单处理模式:受控表单 + 校验
指南给出一个完整的受控表单范式:状态单点持有、提交前统一校验、错误逐字段展示:
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> ) }可复用的要点:
- 错误对象与数据对象分离:
FormErrors与FormData一一对应但类型独立,校验结果不污染业务数据; validate()返回布尔值:决定提交是否继续,错误通过setErrors落到对应字段下方;- 输入用函数式更新:
onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}保证多字段并发更新时不会丢字段; - 字段级约束先行:
name必填 + 200 字符上限,先 trim 再判断,避免纯空格通过校验。
AAS 的 ShortlistReview.tsx 是受控表单的完整生产范例:goal(textarea)与target(select,取值为'codex:project' | 'claude:project')都是受控 state;Copy agent brief按钮在!goal.trim()时禁用;复制动作封装进copy()并捕获剪贴板失败(navigator.clipboard在非 HTTPS 或受限环境下可能不可用),降级提示「Clipboard unavailable」,这正是表单健壮性的体现。
错误边界模式:Error Boundary
React 的类组件错误边界可以捕获子树中的渲染错误、生命周期错误与构造函数错误,防止整个应用白屏:
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>三个关键点:
getDerivedStateFromError是静态方法,把错误转成 UI 状态,触发降级渲染;componentDidCatch拿到错误与组件栈,用于上报监控(示例仅console.error,可替换为 Sentry 等);- 「Try again」按钮通过
setState({ hasError: false })重置边界,实现软恢复。
需要注意:错误边界不能捕获事件处理器、异步代码(setTimeout、Promise)、服务端渲染与自身抛出的错误——这些场景仍需 try/catch。AAS 的 workbenchReview.ts 定义的WorkbenchImportError就是异步校验路径上的错误类型化实践:解析 stack/plan/evidence 产物失败时抛出带明确消息的专用错误,UI 层通过displayError展示而非让异常静默穿透。
动画模式:Framer Motion
列表动画与模态动画
AnimatePresence负责卸载动画(exit),motion.div的initial / animate / exit三态定义进出场:
import { motion, AnimatePresence } from 'framer-motion' // ✅ List animations 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> ) } // ✅ Modal animations 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> ) }实践建议:动画时长控制在 0.2–0.4s(示例 0.3s),避免喧宾夺主;key必须稳定(用market.id而非数组下标),否则列表增删时 AnimatePresence 无法正确匹配进出场节点。动画本质是增强体验而非内容本身——若项目以数据密度为主(如 AAS 目录),应优先保证列表性能,再考虑视觉动效。
可访问性模式:键盘导航与焦点管理
键盘导航(Dropdown)
让下拉菜单完全可用键盘操作,是 Web 可访问性(WCAG)的硬性要求:
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> ) }实现细节:
ArrowDown / ArrowUp用Math.min / Math.max钳制索引边界,不会越界;- 每次方向键移动都
e.preventDefault(),避免页面滚动与下拉选择冲突; Enter选中当前项并关闭,Escape仅关闭;role="combobox"+aria-expanded+aria-haspopup="listbox"把键盘语义告知读屏器。
焦点管理(Modal)
弹窗打开时聚焦到弹窗、关闭时把焦点还给触发元素,是模态组件正确性的分水岭:
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 }要点:tabIndex={-1}让 div 可被编程聚焦但不进入 Tab 序;aria-modal="true"告知读屏器背景内容不可交互;打开/关闭两个分支分别在 effect 中保存与还原焦点。完整的模态还应实现焦点陷阱(Tab 循环限制在弹窗内),可结合需求进一步扩展。
AAS 前端对无障碍同样有系统性投入:如 SkillCard.tsx 为图标按钮提供aria-label、用aria-pressed表达短名单选中态,ShortlistReview.tsx 为状态提示使用role="status"/role="alert",Workbench.tsx 的导入区用aria-live="polite"播报校验结果——这些属性与指南中的键盘/焦点模式互为补充,构成完整的可访问性实践闭环。
模式选型原则与落地建议
指南结尾给出核心提醒:Modern frontend patterns enable maintainable, performant user interfaces. Choose patterns that fit your project complexity.(现代前端模式带来可维护、高性能的 UI,请按项目复杂度选择合适的模式。)
结合 AAS 仓库实践,可以总结出如下选型清单:
| 场景 | 推荐模式 | 仓库参照 |
|---|---|---|
| UI 结构复用 | 组合优于继承 / 复合组件 | SkillCard.tsx |
| 跨层共享状态 | Context + Reducer(或 Provider + Hook) | SkillContext.tsx |
| 复用逻辑 | 自定义 Hooks(useToggle / useQuery / useDebounce) | useSkillShortlist.ts、usePageMeta.ts |
| 重计算/回调稳定 | useMemo / useCallback / React.memo | SkillCard.tsx |
| 首屏体积 | lazy + Suspense 路由级分割 | App.tsx |
| 长列表 | 虚拟化(@tanstack/react-virtual) | App.performance.test.tsx |
| 表单 | 受控组件 + 独立校验状态 | ShortlistReview.tsx |
| 异常降级 | 类组件 Error Boundary | workbenchReview.ts 的错误类型化设计 |
| 动效 | Framer Motion(AnimatePresence) | —(按需引入) |
| 可访问性 | 键盘导航 + 焦点管理 + ARIA 语义 | SkillCard.tsx、ShortlistReview.tsx |
最后要强调的是:技能元信息把风险等级标为critical,其 Limitations 明确「不要将输出视为环境特定验证、测试或专家评审的替代品」。因此,无论模式本身多成熟,落地时都应配套单元测试与真实环境验证——AAS 仓库为目录搜索、短名单、Workbench 产物校验等逻辑都维护了对应测试(见 apps/web-app/src 下各__tests__目录),这正是「模式 + 验证」闭环的最佳示范。
【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,445+ agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/gh_mirrors/an/agentic-awesome-skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考