mcpField
R
react-patterns

react-patterns

github.com/jezweb开发1.1k1.3k 次安装3 周前更新
代码仓库

关于 react-patterns

面向 React 19、Vite 和 Cloudflare Workers 项目的 React 开发模式指南,可用于编写组件、代码评审和重构。内容按优先级覆盖性能、组件组合、数据获取及平台相关注意事项。

主要功能

  1. 01按优先级整理性能规则
  2. 02减少异步请求瀑布
  3. 03控制打包体积与组件重渲染
  4. 04组件组合与架构建议
  5. 05React 19 专属模式
  6. 06Vite 与 Cloudflare 注意事项

适用场景

  1. 01编写或评审 React 组件
  2. 02排查重复渲染或性能变慢
  3. 03重构属性过多的组件
  4. 04合并前检查代码变更

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jezweb/claude-skills --skill react-patterns

安装后在 Claude Code 中输入/react-patterns确认已加载。

详细介绍

React 模式

适用于 React 19 + Vite + Cloudflare Workers 项目的性能与组合模式。编写新组件时可将其作为检查清单,审查现有代码时可将其作为指南,遇到性能缓慢或结构混乱时可将其作为重构手册。

规则按影响程度排序。先修复 CRITICAL 问题,再处理 MEDIUM 问题。

适用场景

  • 编写新的 React 组件或页面
  • 审查代码中的性能问题
  • 重构属性过多或重复渲染的组件
  • 调试“为什么这么慢?”或“为什么会重新渲染?”
  • 构建可复用的组件库
  • 合并前进行代码审查

1. 消除瀑布流(CRITICAL)

本可并行却按顺序执行的异步调用。这是头号性能杀手。

模式问题修复方法
按顺序等待const a = await getA(); const b = await getB();const [a, b] = await Promise.all([getA(), getB()]);
在子组件中获取数据父组件渲染后,子组件获取数据,然后孙组件再获取数据将数据获取提升到最近的共同祖先组件,并向下传递数据
Suspense 级联多个 Suspense 边界依次完成用一个 Suspense 边界包裹所有异步同级组件
分支判断前等待const data = await fetch(); if (condition) { use(data); }将 await 移入分支中——不要获取可能用不到的数据
导入后再渲染const Component = await import('./Heavy'); return <Component />使用 React.lazy() + <Suspense>——立即渲染后备内容

如何查找:在组件中搜索 await。每个 await 都可能导致瀑布流。如果两个 await 彼此独立,就应该并行执行。

2. 包体积(CRITICAL)

用户下载的每 KB,都意味着他们要多等待一会儿。

模式问题修复方法
汇总导入import { Button } from '@/components' 会引入整个汇总文件import { Button } from '@/components/ui/button'——直接导入
未进行代码拆分每个页面都会加载体积庞大的组件React.lazy(() => import('./HeavyComponent')) + <Suspense>
加载时引入第三方代码应用渲染前就加载分析/跟踪代码在 hydration 后加载:useEffect(() => { import('./analytics') }, [])
导入整个库import _ from 'lodash'(70KB)import debounce from 'lodash/debounce'(1KB)
Lucide 树摇优化import * as Icons from 'lucide-react'(所有图标)显式映射:import { Home, Settings } from 'lucide-react'
React 重复库打包了自己的 React → “Cannot read properties of null”在 vite.config.ts 中设置 resolve.dedupe: ['react', 'react-dom']

如何查找:npx vite-bundle-visualizer——查看包中包含的内容。

3. 组合架构(HIGH)

组件的组织方式比优化方式更重要。

模式问题修复方法
布尔属性泛滥<Card isCompact isClickable showBorder hasIcon isLoading>使用明确的变体:<CompactCard>、<ClickableCard>
复合组件包含 15 个属性的复杂组件拆分为 <Dialog>、<Dialog.Trigger>、<Dialog.Content>,并共享上下文
renderX 属性<Layout renderSidebar={...} renderHeader={...} renderFooter={...}>使用 children 和具名插槽:<Layout><Sidebar /><Header /></Layout>
提升状态同级组件无法共享状态将状态移至父组件或上下文提供者
提供者实现消费者代码了解状态管理的内部细节提供者公开接口 { state, actions, meta }——隐藏具体实现
内联组件function Parent() { function Child() { ... } return <Child /> }在 Parent 外定义 Child——内联组件每次渲染都会重新挂载

检验标准:如果一个组件有 5 个以上的布尔属性,就应该采用组合方式,而不是继续增加属性。

4. 防止重复渲染(中)

并非所有重复渲染都是坏事。只修复会导致明显卡顿或浪费计算资源的重复渲染。

模式问题修复方法
默认对象/数组属性function Foo({ items = [] }) → 每次渲染都会创建新的数组引用提升到外部:const DEFAULT = []; function Foo({ items = DEFAULT })
在 effect 中派生状态useEffect(() => setFiltered(items.filter(...)), [items])在渲染期间派生:const filtered = useMemo(() => items.filter(...), [items])
对象依赖项如果 config 是 {},useEffect(() => {...}, [config]) 会在每次渲染时触发使用原始值依赖项:useEffect(() => {...}, [config.id, config.type])
订阅未使用的状态组件读取 { user, theme, settings },但只使用 user拆分上下文或使用选择器:useSyncExternalStore
将临时值存入状态在 mousemove 事件中使用 const [mouseX, setMouseX] = useState(0)对频繁变化但不需要触发重复渲染的值使用 useRef
内联回调属性<Button onClick={() => doThing(id)} /> — 每次渲染都会创建新函数使用 useCallback 或函数式 setState:<Button onClick={handleClick} />

如何查找:使用 React DevTools Profiler 中的 “Why did this render?”,或在开发环境中使用 <React.StrictMode> 的双重渲染。

5. React 19 特性(中)

React 19 中发生变化或新增的模式。

模式旧版(React 18)新版(React 19)
表单状态useFormStateuseActionState — 已更名
转发 refforwardRef((props, ref) => ...)function Component({ ref, ...props }) — ref 是普通属性
上下文useContext(MyContext)use(MyContext) — 可在条件语句和循环中使用
待处理 UI手动管理加载状态使用 useTransition + startTransition 处理非紧急更新
路由级懒加载仅适用于 createBrowserRouter仍然如此 — 在 <BrowserRouter> 中,<Route lazy={...}> 会被静默忽略
乐观更新手动管理状态useOptimistic hook
元数据使用 Helmet 或手动管理 <head>在组件 JSX 中使用 <title>、<meta>、<link> — 会自动提升到 <head>

6. 渲染性能(中)

模式问题修复方法
加载时布局偏移异步数据到达时,内容会跳动使用尺寸与最终布局匹配的骨架屏
直接为 SVG 添加动画SVG 动画卡顿用 <div> 包裹,并为该 div 添加动画
渲染大型列表表格/列表中有 1000 项以上内容使用 @tanstack/react-virtual 进行虚拟化渲染
content-visibility可长距离滚动的内容会在页面加载时全部渲染对屏幕外的区块设置 content-visibility: auto
使用 && 条件渲染{count && <Items />} 在 count 为 0 时会渲染 0使用三元表达式:{count > 0 ? <Items /> : null}

7. 数据获取(中)

模式问题修复方法
未进行去重3 个组件获取相同的数据使用 TanStack Query 或 SWR — 自动去重并缓存
挂载时获取数据useEffect(() => { fetch(...) }, []) — 会产生瀑布请求,且没有缓存或去重使用 TanStack Query:useQuery({ queryKey: ['users'], queryFn: fetchUsers })
没有乐观更新用户点击保存后等待 2 秒,才看到更改使用带有 onMutate 的 useMutation,即时提供视觉反馈
interval 中的过时闭包setInterval 捕获了过时状态使用 useRef 保存 interval ID 和当前值
轮询时未清理在 useEffect 中使用 setInterval,但没有调用 clearInterval返回清理函数:useEffect(() => { const id = setInterval(...); return () => clearInterval(id); })

8. Vite + Cloudflare 特定问题(中)

模式问题修复方法
import.meta.env in Node scripts未定义——仅在经 Vite 处理的文件中有效使用 vite 中的 loadEnv()
React 重复实例库打包了自己的 React在 vite.config.ts 中使用 resolve.dedupe + optimizeDeps.include
Radix Select 空字符串<SelectItem value=""> 会抛出错误使用哨兵值:<SelectItem value="__any__">
React Hook Form null{...field} 会将 null 传递给 Input手动展开:value={field.value ?? ''}
边缘环境中的环境变量Workers 中不存在 process.env使用 c.env(Hono 上下文)或 import.meta.env(Vite 构建时)

用作审查清单

审查代码时,每个 PR 都要检查类别 1-3(严重 + 高)。仅在关注性能时检查类别 4-8。

/react-patterns [file or component path]

阅读文件,按优先级顺序对照规则检查,并按以下格式报告发现:

file:line — [rule] description of issue

评论

支持 Markdown