Token导航 LogoToken导航

Agent Skill · 研究检索

react-render-optimization(React render optimization 搜索)

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

react-render-optimization 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在研究检索类的 73,675 个 Skill 里排第 14,963。

总安装 8,176周安装 334GitHub Stars 173下载 2,645核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:react-render-optimization(React render optimization 搜索)
来源仓库:https://github.com/patternsdev/skills
仓库路径:skills/react-render-optimization
安装命令:
npx skills add https://github.com/patternsdev/skills --skill react-render-optimization
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/patternsdev/skills --skill react-render-optimization

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

深入分析 React 渲染机制,提出减少无效更新的具体优化手段。适用于父组件频繁更新导致子组件意外重渲染的性能敏感场景。可使用 React.memo、useMemo、useCallback 等手段精细化控制更新粒度。需警惕过度 memoization 带来的额外计算开销,按需选择性应用。

结合 Profiler API 量化优化效果,形成数据驱动的决策依据。

来源仓库内路径:skills/react-render-optimization

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/patternsdev/skills --skill react-render-optimi…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

react-render-optimization 怎么安装?

在终端执行 npx skills add https://github.com/patternsdev/skills --skill react-render-optimization。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

react-render-optimization 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

react-render-optimization 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

react-render-optimization 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

react-render-optimization 用的人多吗?

公开数据是累计安装 8,176 次,近一周安装 334 次,下载 2,645 次,GitHub 173 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

react-render-optimization 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
react-render-optimization

React render optimization 搜索 · 本页

8,176173需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

React Render Optimization

Table of Contents

Practical patterns for eliminating unnecessary re-renders, reducing rendering cost, and keeping React UIs responsive. These patterns apply to any React application — whether you're using Vite, Next.js, Remix, or a custom setup.

When to Use

Reference these patterns when:

  • Components re-render more often than expected
  • UI feels sluggish during typing, scrolling, or interactions
  • Profiler shows wasted renders in the component tree
  • Building performance-sensitive features (dashboards, editors, lists)
  • Reviewing or refactoring existing React components

Instructions

  • Apply these patterns during code generation, review, and refactoring. When you see an anti-pattern, suggest the corrected version with an explanation.

Details

Overview

React re-renders a component whenever its state changes, a parent re-renders, or context it consumes updates. Most re-renders are harmless, but when they trigger expensive computation, deep trees, or layout thrashing they become visible to users.

The patterns below are ordered by impact — address the biggest wins first before reaching for micro-optimizations.


1. Compute Derived Values During Render — Don't Store Them

Impact: HIGH — Eliminates an entire category of bugs and unnecessary state.

Storing values that can be computed from existing state or props creates synchronization problems and extra re-renders. Compute them inline instead.

Avoid — redundant state that drifts:

function ProductList({ products }: { products: Product[] }) {
  const [search, setSearch] = useState('')
  const [filtered, setFiltered] = useState(products)

  useEffect(() => {
    setFiltered(products.filter(p =>
      p.name.toLowerCase().includes(search.toLowerCase())
    ))
  }, [products, search])

  return (
    <>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      {filtered.map(p => <ProductCard key={p.id} product={p} />)}
    </>
  )
}

Prefer — derive during render (cheap derivations use plain const):

function ProductList({ products }: { products: Product[] }) {
  const [search, setSearch] = useState('')

  // Cheap derivation — plain const, no useMemo needed
  const hasSearch = search.length > 0
  const normalizedSearch = search.toLowerCase()

  // Expensive derivation — useMemo is justified when iterating large arrays
  const filtered = useMemo(
    () => products.filter(p =>
      p.name.toLowerCase().includes(normalizedSearch)
    ),
    [products, normalizedSearch]
  )

  return (
    <>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      {hasSearch && <ClearButton />}
      {filtered.map(p => <ProductCard key={p.id} product={p} />)}
    </>
  )
}

When to use useMemo vs a plain const:

  • Plain const — boolean flags, string formatting, simple arithmetic, object property access, .length checks. These are essentially free and useMemo overhead is not worth it.
  • useMemo — filtering/sorting arrays, building data structures, JSON.parse, expensive transformations, anything that iterates collections or involves O(n) work.

The rule: if the expression returns a primitive or is a single property access, skip useMemo. If it iterates or transforms data, wrap it.

React Compiler note: If React Compiler is enabled, it auto-memoizes expressions and you can skip manual useMemo calls.

2. Subscribe to Coarse-Grained State, Not Raw Values

Impact: HIGH — Prevents re-renders on irrelevant changes.

If your component only cares about a derived boolean (e.g., "is mobile?"), don't subscribe to the raw value that changes continuously.

Avoid — re-renders on every pixel:

function Sidebar() {
  const width = useWindowWidth() // fires on every resize
  const isMobile = width < 768
  return <nav className={isMobile ? 'mobile' : 'desktop'}>...</nav>
}

Prefer — re-renders only when the boolean flips:

function Sidebar() {
  const isMobile = useMediaQuery('(max-width: 767px)')
  return <nav className={isMobile ? 'mobile' : 'desktop'}>...</nav>
}

This applies broadly: subscribe to isLoggedIn rather than the entire user object, hasItems rather than the full cart array, etc.


3. Extract Expensive Subtrees into Memoized Components

Impact: HIGH — Enables early returns and skip-rendering.

When a parent has fast paths (loading, error, empty), expensive children still compute if they live in the same component. Extract them so React can skip their render entirely.

Avoid — avatar computation runs even during loading:

function Profile({ user, loading }: Props) {
  const avatar = useMemo(() => processAvatar(user), [user])

  if (loading) return <Skeleton />
  return <div><img src={avatar} /></div>
}

Prefer — computation skipped when loading:

const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
  const avatar = useMemo(() => processAvatar(user), [user])
  return <img src={avatar} />
})

function Profile({ user, loading }: Props) {
  if (loading) return <Skeleton />
  return <div><UserAvatar user={user} /></div>
}
React Compiler note: The compiler auto-memoizes, making manual memo() wrapping less necessary. But extracting components for early returns is still valuable.

4. Use Lazy State Initialization

Impact: MEDIUM — Avoids wasted computation on every render.

When useState receives a function call as its initial value, that call executes on every render even though the result is only used once. Pass a function reference instead.

Avoid — buildIndex() runs every render:

const [index, setIndex] = useState(buildSearchIndex(items))

Prefer — runs only on mount:

const [index, setIndex] = useState(() => buildSearchIndex(items))

Use lazy init for: JSON.parse, localStorage reads, building data structures, heavy transformations. Skip it for simple primitives like useState(0) or useState(false).


5. Use Functional setState for Stable Callbacks

Impact: MEDIUM — Removes state variables from dependency arrays.

When a callback only needs the previous state to compute the next state, use the functional form. This eliminates the state variable from the dependency array and produces a stable callback identity.

Avoid — callback changes when count changes:

const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(count + 1), [count])

Prefer — callback is always stable:

const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [])

6. Put Interaction Logic in Event Handlers, Not Effects

Impact: MEDIUM — Avoids re-running side effects on dependency changes.

If a side effect is triggered by a user action (click, submit, drag), run it in the event handler. Modeling it as state + effect causes re-runs when unrelated dependencies change.

Avoid — effect re-runs when theme changes:

function Form() {
  const [submitted, setSubmitted] = useState(false)
  const theme = useContext(ThemeContext)

  useEffect(() => {
    if (submitted) {
      post('/api/register')
      showToast('Registered', theme)
    }
  }, [submitted, theme])

  return <button onClick={() => setSubmitted(true)}>Submit</button>
}

Prefer — logic in the handler:

function Form() {
  const theme = useContext(ThemeContext)

  function handleSubmit() {
    post('/api/register')
    showToast('Registered', theme)
  }

  return <button onClick={handleSubmit}>Submit</button>
}

7. Use useRef for Transient, High-Frequency Values

Impact: MEDIUM — Prevents re-renders on rapid updates.

Values that change very frequently (mouse position, scroll offset, interval ticks) but don't need to drive re-renders should live in a ref. Update the DOM directly when needed.

Avoid — re-renders on every mouse move:

function Cursor() {
  const [x, setX] = useState(0)

  useEffect(() => {
    const handler = (e: MouseEvent) => setX(e.clientX)
    window.addEventListener('mousemove', handler)
    return () => window.removeEventListener('mousemove', handler)
  }, [])

  return <div style={{ transform: `translateX(${x}px)` }} />
}

Prefer — zero re-renders:

function Cursor() {
  const ref = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const handler = (e: MouseEvent) => {
      if (ref.current) {
        ref.current.style.transform = `translateX(${e.clientX}px)`
      }
    }
    window.addEventListener('mousemove', handler)
    return () => window.removeEventListener('mousemove', handler)
  }, [])

  return <div ref={ref} />
}

8. Use startTransition for Non-Urgent Updates

Impact: MEDIUM — Keeps high-priority updates (typing, clicking) responsive.

Wrap non-urgent state updates in startTransition so React can interrupt them for urgent work. This is especially useful for search filtering, tab switching, and list re-sorting.

Avoid — typing blocks while list filters:

function Search({ items }: { items: Item[] }) {
  const [query, setQuery] = useState('')
  const [filtered, setFiltered] = useState(items)

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setQuery(e.target.value)
    setFiltered(items.filter(i => i.name.includes(e.target.value)))
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      <List items={filtered} />
    </>
  )
}

Prefer — input stays responsive:

import { useState, useTransition } from 'react'

function Search({ items }: { items: Item[] }) {
  const [query, setQuery] = useState('')
  const [filtered, setFiltered] = useState(items)
  const [isPending, startTransition] = useTransition()

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setQuery(e.target.value)
    startTransition(() => {
      setFiltered(items.filter(i => i.name.includes(e.target.value)))
    })
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <List items={filtered} />
    </>
  )
}

9. Defer State Reads to the Point of Use

Impact: MEDIUM — Avoids subscriptions to state you only read in callbacks.

Don't call hooks like useSearchParams() if you only read the value inside an event handler. Read it on demand instead.

Avoid — component re-renders on every URL change:

function ShareButton({ id }: { id: string }) {
  const [searchParams] = useSearchParams()

  const handleShare = () => {
    const ref = searchParams.get('ref')
    share(id, { ref })
  }

  return <button onClick={handleShare}>Share</button>
}

Prefer — reads on demand, no subscription:

function ShareButton({ id }: { id: string }) {
  const handleShare = () => {
    const params = new URLSearchParams(window.location.search)
    share(id, { ref: params.get('ref') })
  }

  return <button onClick={handleShare}>Share</button>
}

10. Use Stable References for Default Props

Impact: MEDIUM — Prevents memo() from being defeated by new object/array literals.

Passing [] or {} as default prop values creates new references every render, defeating memoization on child components.

Avoid — new array each render:

function Dashboard({ tabs = [] }: { tabs?: Tab[] }) {
  return <TabBar tabs={tabs} /> {/* TabBar re-renders every time */}
}

Prefer — stable reference:

const EMPTY_TABS: Tab[] = []

function Dashboard({ tabs = EMPTY_TABS }: { tabs?: Tab[] }) {
  return <TabBar tabs={tabs} />
}

11. CSS content-visibility for Long Lists

Impact: HIGH — 5-10x faster initial render for long scrollable content.

Apply content-visibility: auto to off-screen items so the browser skips their layout and paint until they scroll into view.

.list-item {
  content-visibility: auto;
  contain-intrinsic-size: 0 80px; /* estimated height */
}
function MessageList({ messages }: { messages: Message[] }) {
  return (
    <div style={{ overflowY: 'auto', height: '100vh' }}>
      {messages.map(msg => (
        <div key={msg.id} className="list-item">
          <MessageCard message={msg} />
        </div>
      ))}
    </div>
  )
}

For 1,000 items, the browser skips layout and paint for ~990 off-screen items. Combine with virtualization (e.g., react-window, @tanstack/react-virtual) for truly massive lists.


12. Hoist Static JSX Outside Components

Impact: LOW — Avoids re-creating identical elements.

JSX elements that never change can be lifted to module scope. React reuses the same object reference across renders.

Avoid — recreated every render:

function Page() {
  return (
    <main>
      <footer>
        <p>Copyright 2026 Acme Inc.</p>
      </footer>
    </main>
  )
}

Prefer — created once:

const footer = (
  <footer>
    <p>Copyright 2026 Acme Inc.</p>
  </footer>
)

function Page() {
  return <main>{footer}</main>
}

Most impactful for large SVG elements which are expensive to recreate.

React Compiler note: The compiler auto-hoists static JSX, making this manual optimization unnecessary.

13. Initialize Expensive Operations Once Per App

Impact: LOW-MEDIUM — Avoids duplicate init in Strict Mode and remounts.

App-wide initialization (analytics, auth checks, service workers) should not live in useEffect — components remount in development and in concurrent features. Use a module-level guard.

Avoid — runs twice in dev, again on remount:

function App() {
  useEffect(() => {
    initAnalytics()
    checkAuth()
  }, [])
  return <Router />
}

Prefer — once per app load:

let initialized = false

function App() {
  useEffect(() => {
    if (initialized) return
    initialized = true
    initAnalytics()
    checkAuth()
  }, [])
  return <Router />
}

Or initialize at the module level in your entry file (main.tsx), outside any component.


14. Store Event Handlers in Refs for Stable Subscriptions

Impact: LOW — Prevents effect re-subscriptions.

When a custom hook subscribes to an event and accepts a callback, store the callback in a ref so the subscription doesn't tear down and recreate on every render.

Avoid — re-subscribes when handler changes:

function useWindowEvent(event: string, handler: (e: Event) => void) {
  useEffect(() => {
    window.addEventListener(event, handler)
    return () => window.removeEventListener(event, handler)
  }, [event, handler])
}

Prefer — stable subscription:

function useWindowEvent(event: string, handler: (e: Event) => void) {
  const saved = useRef(handler)
  useEffect(() => { saved.current = handler }, [handler])

  useEffect(() => {
    const listener = (e: Event) => saved.current(e)
    window.addEventListener(event, listener)
    return () => window.removeEventListener(event, listener)
  }, [event])
}

If using React 19+, useEffectEvent provides this pattern as a built-in:

import { useEffectEvent } from 'react'

function useWindowEvent(event: string, handler: (e: Event) => void) {
  const onEvent = useEffectEvent(handler)
  useEffect(() => {
    window.addEventListener(event, onEvent)
    return () => window.removeEventListener(event, onEvent)
  }, [event])
}

15. Prevent Hydration Flicker for Client-Only Data

Impact: MEDIUM — Eliminates flash of wrong content during SSR hydration.

When rendering depends on client-only data (localStorage, cookies), an inline script can set the correct value before React hydrates — avoiding both SSR errors and a visible flash.

function ThemeRoot({ children }: { children: React.ReactNode }) {
  return (
    <>
      <div id="app-root">{children}</div>
      <script
        dangerouslySetInnerHTML={{
          __html: `(function(){
            try {
              var t = localStorage.getItem('theme') || 'light';
              document.getElementById('app-root').dataset.theme = t;
            } catch(e) {}
          })();`,
        }}
      />
    </>
  )
}

This approach works in any SSR setup — Next.js, Remix, or a custom Vite SSR pipeline.


16. Never Define Components Inside Components

Impact: HIGH — Causes remounting, state loss, and wasted DOM work every render.

When you define a component inside another component's render, React creates a new component type on every render. This means the entire subtree unmounts and remounts — losing all state, DOM nodes, and effect cleanup/setup.

Avoid — Row is a new type every render:

function Table({ data }: { data: Item[] }) {
  // This creates a NEW component type on every render
  function Row({ item }: { item: Item }) {
    const [selected, setSelected] = useState(false)
    return <tr onClick={() => setSelected(!selected)}>{item.name}</tr>
  }

  return <table>{data.map(item => <Row key={item.id} item={item} />)}</table>
}

Prefer — Row defined at module scope:

function Row({ item }: { item: Item }) {
  const [selected, setSelected] = useState(false)
  return <tr onClick={() => setSelected(!selected)}>{item.name}</tr>
}

function Table({ data }: { data: Item[] }) {
  return <table>{data.map(item => <Row key={item.id} item={item} />)}</table>
}

This also applies to components defined inside useMemo, useCallback, or any other hook. Always define components at module scope or as static properties.


17. Use useDeferredValue for Expensive Derived Renders

Impact: HIGH — Keeps the UI responsive while expensive subtrees re-render in the background.

useDeferredValue tells React to defer re-rendering components that depend on a fast-changing value. Unlike useTransition (which wraps the state update), useDeferredValue wraps the consumption — useful when you don't control the state setter.

Avoid — every keystroke blocks the UI:

function SearchPage({ query }: { query: string }) {
  // Expensive: filters and renders 10,000 items on every keystroke
  const results = filterItems(query)
  return <ResultsList items={results} />
}

Prefer — input stays responsive, results update in background:

import { useDeferredValue, useMemo } from 'react'

function SearchPage({ query }: { query: string }) {
  const deferredQuery = useDeferredValue(query)
  const isStale = query !== deferredQuery

  const results = useMemo(() => filterItems(deferredQuery), [deferredQuery])

  return (
    <div style={{ opacity: isStale ? 0.7 : 1 }}>
      <ResultsList items={results} />
    </div>
  )
}

When to use useDeferredValue vs useTransition:

  • useTransition — you control the state setter and can wrap it in startTransition
  • useDeferredValue — the value comes from props, a parent, or a library you don't control

18. Use Explicit Checks in Conditional Rendering

Impact: MEDIUM — Prevents rendering 0, NaN, or empty strings to the DOM.

The && operator in JSX short-circuits on falsy values — but 0, NaN, and "" are falsy yet still render as visible text nodes.

Avoid — renders 0 to the DOM when count is zero:

function NotificationBadge({ count }: { count: number }) {
  return <div>{count && <Badge>{count}</Badge>}</div>
  // When count is 0, renders: <div>0</div>
}

Prefer — explicit boolean check:

function NotificationBadge({ count }: { count: number }) {
  return <div>{count > 0 && <Badge>{count}</Badge>}</div>
}

// Or use a ternary for clarity
function NotificationBadge({ count }: { count: number }) {
  return <div>{count > 0 ? <Badge>{count}</Badge> : null}</div>
}

This applies to any value that might be 0, NaN, or "" — array lengths, string values, numeric props. Always use an explicit boolean expression (> 0, !== '', != null) rather than relying on truthiness.


19. Narrow Effect Dependencies to Primitives

Impact: MEDIUM — Prevents effects from re-running when unrelated object properties change.

When an effect only needs one property from an object, extract it before the dependency array. Passing the whole object causes re-runs whenever any property changes.

Avoid — effect re-runs when user.name or user.avatar changes:

function UserStatus({ user }: { user: User }) {
  useEffect(() => {
    updatePresence(user.id)
  }, [user]) // re-runs on ANY user property change
}

Prefer — only re-runs when the ID changes:

function UserStatus({ user }: { user: User }) {
  const { id } = user
  useEffect(() => {
    updatePresence(id)
  }, [id])
}

This also applies to hook return values. If useQuery returns {data, status, fetchStatus} and your effect only cares about status, destructure first.


20. Split Combined Hook Computations

Impact: MEDIUM — Prevents re-renders for consumers that only need part of a hook's output.

When a custom hook computes multiple unrelated values, a change in one forces re-renders in all consumers — even those that only read the unchanged value.

Avoid — changing total re-renders components that only need average:

function useStats(items: number[]) {
  return useMemo(() => ({
    total: items.reduce((a, b) => a + b, 0),
    average: items.reduce((a, b) => a + b, 0) / items.length,
    max: Math.max(...items),
  }), [items])
}

Prefer — split into focused hooks:

function useTotal(items: number[]) {
  return useMemo(() => items.reduce((a, b) => a + b, 0), [items])
}

function useAverage(items: number[]) {
  return useMemo(() => items.reduce((a, b) => a + b, 0) / items.length, [items])
}

function useMax(items: number[]) {
  return useMemo(() => Math.max(...items), [items])
}

Components call only the hook they need. If a single component needs all three, combining them there is fine — the split prevents unnecessary coupling at the hook level.


21. Avoid Layout Thrashing with Batched DOM Reads/Writes

Impact: HIGH — Prevents forced synchronous layouts that block the main thread.

Reading a layout property (e.g., offsetHeight, getBoundingClientRect()) after writing to the DOM forces the browser to recalculate layout synchronously. In a loop, this creates layout thrashing.

Avoid — forces layout recalculation on every iteration:

function resizeCards(cards: HTMLElement[]) {
  cards.forEach(card => {
    const height = card.offsetHeight          // READ (forces layout)
    card.style.minHeight = `${height + 20}px` // WRITE (invalidates layout)
  })
}

Prefer — batch all reads, then all writes:

function resizeCards(cards: HTMLElement[]) {
  // Read phase
  const heights = cards.map(card => card.offsetHeight)

  // Write phase
  cards.forEach((card, i) => {
    card.style.minHeight = `${heights[i] + 20}px`
  })
}

In React, this most commonly occurs in useLayoutEffect or useEffect callbacks that measure and mutate DOM elements. When you need to read layout inside an animation frame, use requestAnimationFrame to batch:

useLayoutEffect(() => {
  const measurements = items.map(el => el.getBoundingClientRect())

  requestAnimationFrame(() => {
    items.forEach((el, i) => {
      el.style.transform = `translateY(${measurements[i].top}px)`
    })
  })
}, [items])

22. Animate SVG Wrappers, Not SVG Elements Directly

Impact: MEDIUM — Avoids repainting the entire SVG on every animation frame.

Animating properties on an SVG element itself (e.g., <svg> or <path>) triggers a full SVG repaint. Wrap the SVG in a <div> and animate the wrapper instead.

Avoid — repaints entire SVG tree:

<motion.svg animate={{ rotate: 360 }} style={{ width: 200, height: 200 }}>
  <ComplexChart />
</motion.svg>

Prefer — only the wrapper repaints:

<motion.div animate={{ rotate: 360 }} style={{ width: 200, height: 200 }}>
  <svg viewBox="0 0 200 200">
    <ComplexChart />
  </svg>
</motion.div>

This also applies to CSS animations. Use transform on a wrapper element rather than animating SVG attributes like cx, cy, or d directly.


23. Suppress Expected Hydration Mismatches

Impact: LOW-MEDIUM — Silences known-safe warnings without hiding real bugs.

Some content is intentionally different between server and client — timestamps, random IDs, user-agent-specific rendering. Use suppressHydrationWarning on those specific elements.

function Comment({ createdAt }: { createdAt: Date }) {
  return (
    <article>
      <p>{comment.body}</p>
      <time suppressHydrationWarning>
        {formatRelativeTime(createdAt)} {/* "2 minutes ago" differs server vs client */}
      </time>
    </article>
  )
}

Apply sparingly and only on leaf elements. Never suppress warnings on container elements — it masks real mismatches in children.


24. React DOM Resource Hints for Vite SPAs

Impact: HIGH — Lets the browser start loading critical resources earlier without framework support.

React 19 adds preload() and preinit() from react-dom — imperative resource hints that work in any React app. In Vite SPAs (which don't get framework-level prefetching), these are especially valuable.

import { preload, preinit } from 'react-dom'

function App() {
  // Preload a font before it's needed
  preload('/fonts/inter-var.woff2', { as: 'font', type: 'font/woff2', crossOrigin: 'anonymous' })

  // Preinit a critical CSS file (loads + applies it)
  preinit('/critical.css', { as: 'style' })

  return <RouterProvider router={router} />
}

On navigation — preload the next page's data and code:

function ProductLink({ id }: { id: string }) {
  const handleHover = () => {
    // Preload the image the next page will need
    preload(`/api/products/${id}/image.webp`, { as: 'image' })
    // Prefetch the route code
    import('./pages/ProductDetail')
  }

  return <Link to={`/products/${id}`} onMouseEnter={handleHover}>View</Link>
}

These are no-ops if the resource is already loaded, so calling them eagerly is safe. For Vite apps without a meta-framework, this is the primary mechanism for resource prioritization.


25. Use useTransition for Route Navigation

Impact: MEDIUM — Keeps the current page interactive while the next route loads.

In Vite SPAs with React.lazy() routes, clicking a navigation link can freeze the UI while the chunk loads and the component renders. Wrapping navigation in startTransition lets React show the old page until the new one is ready.

import { useTransition } from 'react'
import { useNavigate } from 'react-router-dom'

function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
  const navigate = useNavigate()
  const [isPending, startTransition] = useTransition()

  const handleClick = (e: React.MouseEvent) => {
    e.preventDefault()
    startTransition(() => {
      navigate(to)
    })
  }

  return (
    <a
      href={to}
      onClick={handleClick}
      style={{ opacity: isPending ? 0.7 : 1 }}
    >
      {children}
    </a>
  )
}

This prevents the blank-screen flash between lazy-loaded routes and gives you isPending to show a subtle loading indicator on the current page.


Source

Patterns from patterns.dev — framework-agnostic React performance guidance for the broader web engineering community.

平台分布

Codex

34.79%
按下载量换算920

Claude

29.88%
按下载量换算790

Cursor

17.81%
按下载量换算471

Gemini CLI

8.91%
按下载量换算236

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills