
AI 技能AI 插件人工智能开发工具【免费下载链接】claude-code-infrastructure-showcaseExamples of my Claude Code infrastructure with skill auto-activation, hooks, and agents项目地址https://gitcode.com/gh_mirrors/cl/claude-code-infrastructure-showcase点击查看免费下载本文档源自 claude-code-infrastructure-showcase 仓库中 frontend-dev-guidelines 技能的>import { useSuspenseQuery } from tanstack/react-query; import { myFeatureApi } from ../api/myFeatureApi; export const MyComponent: React.FCProps ({ id }) { // No isLoading - Suspense handles it! const { data } useSuspenseQuery({ queryKey: [myEntity, id], queryFn: () myFeatureApi.getEntity(id), }); // data is ALWAYS defined here (not undefined | Data) return div{data.name}/div; }; // Wrap in Suspense boundary SuspenseLoader MyComponent id{123} / /SuspenseLoader关键点在useSuspenseQuery下data的类型是确定已定义的而非Data | undefined这是与useQuery最直观的类型差异配合严格 TypeScript 模式见 typescript-standards.md可以消除大量空值判断。1.3 useSuspenseQuery vs useQuery特性useSuspenseQueryuseQuery加载状态由 Suspense 处理手动isLoading检查数据类型始终已定义Data \| undefined适用场景Suspense 边界内传统组件推荐对象新组件仅遗留代码错误处理Error Boundary手动 error state何时仍使用常规 useQuery维护遗留代码时渐进改造不强行重写非常简单的、未接入 Suspense 的场景需要轮询polling配合后台更新background updates的场景。规则新组件一律优先useSuspenseQuery。二、缓存优先Cache-First策略2.1 设计动机React Query 本身带有内存缓存但默认的useQuery在组件挂载时仍然会发起请求除非命中 staleTime 判定为 fresh。缓存优先策略进一步把先从已有列表缓存中取数、取不到再打 API的逻辑显式写进queryFn从而最大化复用已加载的数据、显著减少重复请求。2.2 缓存优先模式示例import { useSuspenseQuery, useQueryClient } from tanstack/react-query; import { postApi } from ../api/postApi; export function useSuspensePost(postId: number) { const queryClient useQueryClient(); return useSuspenseQuery({ queryKey: [post, postId], queryFn: async () { // Strategy 1: Try to get from list cache first const cachedListData queryClient.getQueryData{ posts: Post[] }([ posts, list ]); if (cachedListData?.posts) { const cachedPost cachedListData.posts.find( (post) post.id postId ); if (cachedPost) { return cachedPost; // Return from cache! } } // Strategy 2: Not in cache, fetch from API return postApi.getPost(postId); }, staleTime: 5 * 60 * 1000, // Consider fresh for 5 minutes gcTime: 10 * 60 * 1000, // Keep in cache for 10 minutes refetchOnWindowFocus: false, // Dont refetch on focus }); }要点说明先查列表/网格缓存再决定是否发 API 请求进入详情页时如果列表页的数据还在缓存中直接复用实现秒开避免冗余请求同一实体在列表页与详情页之间跳转时不重复拉取staleTime数据被认为新鲜的时长在新鲜期内不会重新请求gcTime未被使用的数据在缓存中保留的时长v5 中由原cacheTime更名而来refetchOnWindowFocus: false窗口聚焦时不自动刷新属于用户偏好设定本仓库的默认 QueryClient 配置也一致采用该值见下文查询配置最佳实践。2.3 生产级完整示例在文档的Complete Examples中给出了带博客域blogId postId双参数、同时命中多个视图缓存的完整实现import { useSuspenseQuery, useQueryClient } from tanstack/react-query; import { postApi } from ../api/postApi; import type { Post } from ../types; /** * Hook with cache-first strategy * Checks grid cache before API call */ export function useSuspensePost(blogId: number, postId: number) { const queryClient useQueryClient(); return useSuspenseQueryPost, Error({ queryKey: [post, blogId, postId], queryFn: async () { // 1. Check grid cache first const gridCache queryClient.getQueryData{ rows: Post[] }([ posts-v2, blogId, summary ]) || queryClient.getQueryData{ rows: Post[] }([ posts-v2, blogId, flat ]); if (gridCache?.rows) { const cached gridCache.rows.find(row row.S_ID postId); if (cached) { return cached; // Reuse grid data } } // 2. Not in cache, fetch directly return postApi.getPost(blogId, postId); }, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, refetchOnWindowFocus: false, }); }收益避免重复 API 调用数据若已加载则即时可用缓存未命中时自动回退到 API 请求。三、并行数据获取useSuspenseQueries当需要同时获取多个互相独立的数据源时使用useSuspenseQueries一次性声明所有查询由单个 Suspense 边界统一管理加载import { useSuspenseQueries } from tanstack/react-query; export const MyComponent: React.FC () { const [userQuery, settingsQuery, preferencesQuery] useSuspenseQueries({ queries: [ { queryKey: [user], queryFn: () userApi.getCurrentUser(), }, { queryKey: [settings], queryFn: () settingsApi.getSettings(), }, { queryKey: [preferences], queryFn: () preferencesApi.getPreferences(), }, ], }); // All data available, Suspense handles loading const user userQuery.data; const settings settingsQuery.data; const preferences preferencesQuery.data; return Display user{user} settings{settings} prefs{preferences} /; };收益所有查询并行发出不串行等待单一 Suspense 边界即可返回结果类型安全每个 query 的结果对象可单独解构。文档还给出了仪表盘Dashboard形态的完整示例statsQuery、projectsQuery、notificationsQuery三路并行各自的数据分别喂给StatsCard、ProjectsList、Notifications组件。四、Query Keys 组织规范4.1 命名约定Query Key 是全应用共享的缓存地址必须可预测、可检索、可精确失效。规范格式如下// Entity list [entities, blogId] [entities, blogId, summary] // With view mode [entities, blogId, flat] // Single entity [entity, blogId, entityId] // Related data [entity, entityId, history] [entity, entityId, comments] // User-specific [user, userId, profile] [user, userId, permissions]命名规则以实体名开头列表用复数entities、posts单个实体用单数entity、post包含 ID 以保证唯一性blogId、entityId、userId等逐级追加视图模式/关系词放在末尾summary、flat、history、comments等全应用保持一致Query Key 的构造方式必须统一否则缓存失效将无法命中。4.2 实际 Key 示例与失效模式// From useSuspensePost.ts queryKey: [post, blogId, postId] queryKey: [posts-v2, blogId, summary] // Invalidation patterns queryClient.invalidateQueries({ queryKey: [post, blogId] }); // All posts for form queryClient.invalidateQueries({ queryKey: [post] }); // All posts注意失效invalidate是前缀匹配的[post, blogId]会失效该 blogId 下所有 post 相关查询[post]则失效所有 post 查询。这一特性决定了 Key 的层级设计需要兼顾精确失效单条与批量失效列表两种需求——这也正是缓存优先 失效模式能在本仓库的网格grid/list场景中高效协作的原因。五、API Service 层模式5.1 文件结构每个功能模块feature下建立集中的 API 服务文件作为组件与网络层之间的唯一桥梁features/ my-feature/ api/ myFeatureApi.ts # Service layer该结构与 SKILL.md 中New Feature Checklist的目录约定一致features/{feature-name}/下包含api/、components/、hooks/、helpers/、types/五个子目录。5.2 服务层模式源自 postApi.ts/** * Centralized API service for my-feature operations * Uses apiClient for consistent error handling */ import apiClient from /lib/apiClient; import type { MyEntity, UpdatePayload } from ../types; export const myFeatureApi { /** * Fetch a single entity */ getEntity: async (blogId: number, entityId: number): PromiseMyEntity { const { data } await apiClient.get( /blog/entities/${blogId}/${entityId} ); return data; }, /** * Fetch all entities for a form */ getEntities: async (blogId: number, view: summary | flat): PromiseMyEntity[] { const { data } await apiClient.get( /blog/entities/${blogId}, { params: { view } } ); return data.rows; }, /** * Update entity */ updateEntity: async ( blogId: number, entityId: number, payload: UpdatePayload ): PromiseMyEntity { const { data } await apiClient.put( /blog/entities/${blogId}/${entityId}, payload ); return data; }, /** * Delete entity */ deleteEntity: async (blogId: number, entityId: number): Promisevoid { await apiClient.delete(/blog/entities/${blogId}/${entityId}); }, };关键规范导出一个包含方法的单一对象按功能聚合getEntity / getEntities / updateEntity / deleteEntity统一使用apiClient/lib/apiClient导出的 axios 实例禁止在服务层自行 new axios参数与返回值全部类型化并声明PromiseMyEntity等显式返回类型每个方法带 JSDoc 注释便于 Claude Code / IDE 理解意图错误处理集中化由 apiClient 的拦截器统一处理服务层不再重复 try/catch。本仓库的 SKILL.md 在Data Fetching一节同样强调API Service 层应创建于features/{feature}/api/{feature}Api.ts使用apiClient并为每个 feature 提供集中式方法。六、路由格式规则重要文档特别以前缀式微服务路由为例说明了路径写法——原生产项目中代理proxy将每个微服务映射到路径前缀因此API 路径中不需要/api/前缀// ✅ CORRECT - Path matches the proxy configuration (here: direct service prefixes) await apiClient.get(/blog/posts/123); await apiClient.post(/projects/create, data); await apiClient.put(/users/update/456, updates); await apiClient.get(/email/templates); // ❌ WRONG - In this setup the proxy adds no /api/ prefix await apiClient.get(/api/blog/posts/123); // WRONG here (fine if YOUR backend uses /api/) await apiClient.post(/api/projects/create, data); // WRONG here示例微服务路由映射来自原项目表单服务/blog/*项目服务/projects/*邮件服务/email/*用户服务/users/*为什么在该架构中API 路由由代理配置处理所以不需要/api/前缀。真正的规则是让apiClient中的路径与你自己项目的路由配置保持一致——如果后端在/api/前缀下提供路由则使用/api/...也是正确的。文档中的/blog/...仅是教学示例落地时请替换为你自己的路径。七、Mutations变更操作7.1 基础 Mutation 模式import { useMutation, useQueryClient } from tanstack/react-query; import { myFeatureApi } from ../api/myFeatureApi; import { useMuiSnackbar } from /hooks/useMuiSnackbar; export const MyComponent: React.FC () { const queryClient useQueryClient(); const { showSuccess, showError } useMuiSnackbar(); const updateMutation useMutation({ mutationFn: (payload: UpdatePayload) myFeatureApi.updateEntity(blogId, entityId, payload), onSuccess: () { // Invalidate and refetch queryClient.invalidateQueries({ queryKey: [entity, blogId, entityId] }); showSuccess(Entity updated successfully); }, onError: (error) { showError(Failed to update entity); console.error(Update error:, error); }, }); const handleUpdate () { updateMutation.mutate({ name: New Name }); }; return ( Button onClick{handleUpdate} disabled{updateMutation.isPending} {updateMutation.isPending ? Updating... : Update} /Button ); };要点mutationFn调用 Service 层onSuccess中精确失效对应 Query Key并触发重新获取用户反馈统一走useMuiSnackbar本仓库明确禁止使用react-toastify见 loading-and-error-states.md按钮通过isPending禁用并切换文案。7.2 乐观更新Optimistic Updatesconst updateMutation useMutation({ mutationFn: (payload) myFeatureApi.update(id, payload), // Optimistic update onMutate: async (newData) { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: [entity, id] }); // Snapshot current value const previousData queryClient.getQueryData([entity, id]); // Optimistically update queryClient.setQueryData([entity, id], (old) ({ ...old, ...newData, })); // Return rollback function return { previousData }; }, // Rollback on error onError: (err, newData, context) { queryClient.setQueryData([entity, id], context.previousData); showError(Update failed); }, // Refetch after success or error onSettled: () { queryClient.invalidateQueries({ queryKey: [entity, id] }); }, });乐观更新的标准三步onMutate 快照回滚先取消在途请求 → 读取旧值 → 立即写入新值 → 返回快照作为 context→onError 回滚把 context 中的旧值写回并提示错误→onSettled 刷新无论成败都失效查询保证与服务端最终一致。7.3 带缓存失效的更新与删除生产完整版更新 PostonSuccess中同时失效单条 post[post, blogId, postId]与列表/网格[posts-v2, blogId]保证详情与列表同步刷新export const useUpdatePost () { const queryClient useQueryClient(); const { showSuccess, showError } useMuiSnackbar(); return useMutation({ mutationFn: ({ blogId, postId, data }: UpdateParams) postApi.updatePost(blogId, postId, data), onSuccess: (data, variables) { // Invalidate specific post queryClient.invalidateQueries({ queryKey: [post, variables.blogId, variables.postId] }); // Invalidate list to refresh grid queryClient.invalidateQueries({ queryKey: [posts-v2, variables.blogId] }); showSuccess(Post updated); }, onError: (error) { showError(Failed to update post); console.error(Update error:, error); }, }); };删除 PostonSuccess中手动从缓存中移除该行乐观删行onError中失效列表回滚到真实状态export const useDeletePost () { const queryClient useQueryClient(); const { showSuccess, showError } useMuiSnackbar(); return useMutation({ mutationFn: ({ blogId, postId }: DeleteParams) postApi.deletePost(blogId, postId), onSuccess: (data, variables) { // Remove from cache manually (optimistic) queryClient.setQueryData{ rows: Post[] }( [posts-v2, variables.blogId], (old) ({ ...old, rows: old?.rows.filter(row row.S_ID ! variables.postId) || [] }) ); showSuccess(Post deleted); }, onError: (error, variables) { // Rollback - refetch to get accurate state queryClient.invalidateQueries({ queryKey: [posts-v2, variables.blogId] }); showError(Failed to delete post); }, }); };八、高级查询模式8.1 预取Prefetching在用户真正导航到详情页之前提前把数据放入缓存export function usePrefetchEntity() { const queryClient useQueryClient(); return (blogId: number, entityId: number) { return queryClient.prefetchQuery({ queryKey: [entity, blogId, entityId], queryFn: () myFeatureApi.getEntity(blogId, entityId), staleTime: 5 * 60 * 1000, }); }; } // Usage: Prefetch on hover div onMouseEnter{() prefetch(blogId, id)} Link to{/entity/${id}}View/Link /divprefetchQuery与useSuspenseQuery共享同一缓存预取完成后进入详情页将直接命中缓存实现瞬时渲染。常见触发时机hover、鼠标按下、路由 enter 前等。8.2 仅读缓存、不发请求export function useEntityFromCache(blogId: number, entityId: number) { const queryClient useQueryClient(); // Get from cache, dont fetch if missing const directCache queryClient.getQueryDataMyEntity([entity, blogId, entityId]); if (directCache) return directCache; // Try grid cache const gridCache queryClient.getQueryData{ rows: MyEntity[] }([entities-v2, blogId]); return gridCache?.rows.find(row row.id entityId); }此模式适合有缓存就用、没有就返回 undefined 走降级 UI的场景例如面包屑、行内快捷信息等绝不触发网络请求。8.3 依赖查询Dependent Queries// Fetch user first, then users settings const { data: user } useSuspenseQuery({ queryKey: [user, userId], queryFn: () userApi.getUser(userId), }); const { data: settings } useSuspenseQuery({ queryKey: [user, userId, settings], queryFn: () settingsApi.getUserSettings(user.id), // Automatically waits for user to load due to Suspense });由于 Suspense 语义第二个查询的queryFn会自动等待第一个查询的数据就绪user.id可用后才执行——无需手动维护 enabled 标志这也是比传统useQuery依赖链更简洁的地方。九、apiClient 配置9.1 统一使用 apiClientimport apiClient from /lib/apiClient; // apiClient is a configured axios instance // Automatically includes: // - Base URL configuration // - Cookie-based authentication // - Error interceptors // - Response transformers规范不要创建新的 axios 实例一律使用apiClient以保证基础 URL 配置统一、基于 Cookie 的认证自动附加、错误拦截器集中处理、响应转换器如统一解包data全局生效。9.2 典型错误处理回调import { useMuiSnackbar } from /hooks/useMuiSnackbar; const { showError } useMuiSnackbar(); const { data } useSuspenseQuery({ queryKey: [entity, id], queryFn: () myFeatureApi.getEntity(id), // Handle errors onError: (error) { showError(Failed to load entity); console.error(Load error:, error); }, });9.3 与 Error Boundary 结合import { ErrorBoundary } from react-error-boundary; ErrorBoundary fallback{ErrorDisplay /} onError{(error) console.error(error)} SuspenseLoader ComponentWithSuspenseQuery / /SuspenseLoader /ErrorBoundary整体错误策略是双层结构查询级onError做细粒度提示Snackbar 反馈 控制台日志组件级 Error Boundary 兜底未捕获的渲染错误实现覆盖完整、体验一致的错误处理。十、查询配置最佳实践10.1 默认配置QueryClientProvider 初始化处// In QueryClientProvider setup const queryClient new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutes gcTime: 1000 * 60 * 10, // 10 minutes (was cacheTime) refetchOnWindowFocus: false, // Dont refetch on focus refetchOnMount: false, // Dont refetch on mount if fresh retry: 1, // Retry failed queries once }, }, });这些默认值的含义数据 5 分钟内视为新鲜不重新请求未被使用的数据在缓存中保留 10 分钟窗口聚焦不自动刷新挂载时若数据仍新鲜则不重新获取失败查询仅重试 1 次避免在弱网下放大请求压力。10.2 按查询覆盖默认值// Frequently changing data - shorter staleTime useSuspenseQuery({ queryKey: [notifications, unread], queryFn: () notificationApi.getUnread(), staleTime: 30 * 1000, // 30 seconds }); // Rarely changing data - longer staleTime useSuspenseQuery({ queryKey: [form, blogId, structure], queryFn: () formApi.getStructure(blogId), staleTime: 30 * 60 * 1000, // 30 minutes });经验法则数据变化越频繁staleTime越短通知类 30 秒越稳定的数据staleTime越长表单结构类 30 分钟在新鲜度与请求量之间取得平衡。十一、完整示例11.1 简单实体获取用户资料import React from react; import { useSuspenseQuery } from tanstack/react-query; import { Box, Typography } from mui/material; import { userApi } from ../api/userApi; interface UserProfileProps { userId: string; } export const UserProfile: React.FCUserProfileProps ({ userId }) { const { data: user } useSuspenseQuery({ queryKey: [user, userId], queryFn: () userApi.getUser(userId), staleTime: 5 * 60 * 1000, }); return ( Box Typography varianth5{user.name}/Typography Typography{user.email}/Typography /Box ); }; // Usage with Suspense SuspenseLoader UserProfile userId123 / /SuspenseLoader11.2 并行获取仪表盘import { useSuspenseQueries } from tanstack/react-query; export const Dashboard: React.FC () { const [statsQuery, projectsQuery, notificationsQuery] useSuspenseQueries({ queries: [ { queryKey: [stats], queryFn: () statsApi.getStats(), }, { queryKey: [projects, active], queryFn: () projectsApi.getActiveProjects(), }, { queryKey: [notifications, unread], queryFn: () notificationsApi.getUnread(), }, ], }); return ( Box StatsCard data{statsQuery.data} / ProjectsList projects{projectsQuery.data} / Notifications items{notificationsQuery.data} / /Box ); };十二、落地集成指南12.1 在 Claude Code 工作流中的强制保障本模式文档并非孤立存在它属于frontend-dev-guidelines技能的资源文件data-fetching.md由 SKILL.md 通过渐进式披露Progressive Disclosure机制按需加载Claude 先读取主文件仅在处理数据获取任务时加载本资源避免上下文膨胀符合仓库的500 行规则。在 skill-rules.json 中frontend-dev-guidelines被配置为guardrailblock强制执行fileTriggers.pathPatterns覆盖src/**/*.tsx、frontend/src/**/*.tsx、client/src/**/*.tsx等常见前端目录contentPatterns命中from mui/material;、Grid、makeStyles、material-ui/core等特征一旦命中blockMessage会要求先通过 Skill 工具激活本技能、检查 MUI v7 模式Grid 使用size{{}}属性再继续编辑。这意味着在接入本仓库基础设施的项目中偏离上述数据获取规范的新代码会被 PreToolUse 钩子直接拦截。12.2 通过 setup.ts 自动识别前端技术栈本仓库的安装向导 setup.ts 在detectTechStack函数中自动探测目标项目的技术栈它检查package.json含frontend/、client/、web/、api/、server/、backend/等常见位置中的依赖——命中react标记 React、命中mui/material标记 MUI、命中tanstack/react-query或tanstack/react-router标记 TanStack——并在安装完成后建议首个验证提示词 create a new React component从而触发frontend-dev-guidelines技能自动激活。若你的项目启用了 TanStack Querytanstack/react-query本文所述模式即为该技能推荐的数据获取唯一路径。12.3 迁移到你的项目时的注意事项文档中的/blog/*、/projects/*等路径来自原生产项目的微服务代理配置请替换为你自己的路由前缀若后端在/api/下提供路由则用/api/...apiClient、SuspenseLoader、useMuiSnackbar是项目内部模块对应 SKILL.md 中 Import Aliases 表格里的/lib/apiClient、~components/SuspenseLoader、/hooks/useMuiSnackbar接入时需在你的代码库中实现等价组件Query Key 命名与缓存优先策略依赖团队全员遵守同一约定建议将本文直接纳入技能资源与代码评审检查单。十三、总结现代数据获取模式速查配方Modern Data Fetching Recipe创建 API Servicefeatures/X/api/XApi.ts统一使用apiClient使用 useSuspenseQuery新组件一律在SuspenseLoader包裹下使用缓存优先queryFn内先检查网格/列表缓存命中即返回未命中再发 APIQuery Keys 一致命名[entity, id]列表复数、单条单数、视图/关系词置尾路由格式/blog/route匹配你的代理配置不是/api/blog/routeMutations成功后invalidateQueries精确失效对应 Key含列表错误处理onError回调 useMuiSnackbar提示 Error Boundary 兜底类型安全所有参数与返回值显式标注类型。关联阅读component-patterns.md — Suspense 与组件模式集成loading-and-error-states.md — SuspenseLoader 与错误状态规范complete-examples.md — 完整可运行示例common-patterns.md — 表单、认证、DataGrid 等常见模式frontend-dev-guidelines/SKILL.md — 技能主文件与导航skill-rules.json — 技能触发与强制规则配置赞分享AI 技能AI 插件人工智能开发工具【免费下载链接】claude-code-infrastructure-showcaseExamples of my Claude Code infrastructure with skill auto-activation, hooks, and agents项目地址https://gitcode.com/gh_mirrors/cl/claude-code-infrastructure-showcase点击查看免费下载相关推荐TanStack React Query 的 useSuspenseQuery 完全指南Suspense 数据获取、错误边界与实现原理TanStack React Query 的 useSuspenseQuery 完全指南Suspense 数据获取、错误边界与实现原理 useSuspense前端缓存状态管理TanStack React Query 实战指南在 React 中优雅地获取、缓存与更新异步数据TanStack React Query 实战指南在 React 中优雅地获取、缓存与更新异步数据 React Query现归属于 TanStack Que前端缓存状态管理开源GEO/AEO Tracker的5种实用玩法免费AI可见度追踪完整指南开源GEO/AEO Tracker的5种实用玩法免费AI可见度追踪完整指南 awesome free saas 是一个精心整理的免费 SaaS 工具开源教程文档知识库上一篇如何5分钟解锁英雄联盟全皮肤R3nzSkin国服特供版完全指南下一篇KMS_VL_ALL_AIOWindows与Office企业级批量激活机制的技术实现与架构解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考