正在加载,请稍候…

2026年React性能优化:memo、并发特性与性能分析

实用的React性能优化指南,涵盖React.memo、useMemo、useCallback、useTransition、Profiler API和虚拟列表

2026年React性能优化:memo、并发特性与性能分析

React性能的正确思维模型

大多数React性能问题都是架构问题,而非框架限制。在求助于useMemoReact.memo之前,先理解React为何重新渲染。

React在以下情况下会重新渲染组件:

  1. 其状态发生变化(useStateuseReducer
  2. 其父组件重新渲染并传递了新props
  3. 其订阅的context发生变化

目标不是消除重新渲染,而是消除不必要的重新渲染,并确保必要的重新渲染足够快。

2026年React性能优化:memo、并发特性与性能分析插图

先测量再优化

import { Profiler, ProfilerOnRenderCallback } from 'react'

const onRender: ProfilerOnRenderCallback = (
  id,
  phase,
  actualDuration,
  baseDuration,
) => {
  if (actualDuration > 16) {
    console.warn(`Slow render: ${id} took ${actualDuration.toFixed(2)}ms`)
  }
}

function App() {
  return (
    <Profiler id="ProductList" onRender={onRender}>
      <ProductList />
    </Profiler>
  )
}

打开React DevTools Profiler,记录一次交互,并查找:

  • 渲染频率高于预期的组件
  • 渲染时长超过16ms(60fps下的一帧)的组件

React.memo:何时有用

React.memo阻止组件在其props未变化时重新渲染:

// 不使用memo:每次父组件重新渲染时都会重新渲染
function ExpensiveChart({ data, title }: ChartProps) { ... }

// 使用memo:仅在data或title实际变化时重新渲染
const ExpensiveChart = React.memo(function ExpensiveChart({ data, title }: ChartProps) { ... })

引用相等陷阱

// 错误:memo无效——每次渲染时options都是新对象
function Parent() {
  return <Chart options={{ color: 'blue', width: 300 }} />
}

// 正确:将常量对象移到组件外部
const CHART_OPTIONS = { color: 'blue', width: 300 }
function Parent() {
  return <Chart options={CHART_OPTIONS} />
}

useMemo:缓存昂贵计算

import { useMemo } from 'react'

function ProductList({ products, filters, sortBy }: Props) {
  // 错误:每次渲染都重新计算,即使是不相关的状态变化
  const filteredProducts = products
    .filter(p => filters.every(f => f(p)))
    .sort((a, b) => compareFn(a, b, sortBy))

  // 正确:仅在输入变化时重新计算
  const filteredProducts = useMemo(
    () => products
      .filter(p => filters.every(f => f(p)))
      .sort((a, b) => compareFn(a, b, sortBy)),
    [products, filters, sortBy]
  )

  return <ul>{filteredProducts.map(p => <ProductItem key={p.id} product={p} />)}</ul>
}

稳定的context值

function UserProfile({ userId }: Props) {
  const [theme, setTheme] = useState('light')

  // 错误:每次渲染都创建新的context值对象,所有消费者都重新渲染
  // return <UserContext.Provider value={{ userId, theme, setTheme }}>

  // 正确:仅在userId或theme变化时创建新对象
  const contextValue = useMemo(
    () => ({ userId, theme, setTheme }),
    [userId, theme]
  )

  return (
    <UserContext.Provider value={contextValue}>
      <UserDetails />
    </UserContext.Provider>
  )
}

2026年React性能优化:memo、并发特性与性能分析插图

useCallback:稳定的函数引用

import { useCallback, memo } from 'react'

const ExpensiveChild = memo(function ExpensiveChild({
  onAction,
}: {
  onAction: (id: string) => void
}) {
  return <div>...</div>
})

function Parent() {
  const [count, setCount] = useState(0)

  // 错误:每次渲染都创建新的函数引用——破坏了ExpensiveChild上的memo
  // const handleAction = (id: string) => { ... }

  // 正确:稳定的引用——ExpensiveChild上的memo按预期工作
  const handleAction = useCallback((id: string) => {
    analytics.track('action', { id })
  }, [])

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <ExpensiveChild onAction={handleAction} />
    </>
  )
}

规则useCallback主要用于将回调传递给已记忆的子组件。

useTransition:保持UI响应

React Concurrent Mode允许你将状态更新标记为非紧急。UI在处理后台昂贵更新时保持响应:

import { useState, useTransition } from 'react'

function SearchPage() {
  const [query, setQuery] = useState('')
  const [isPending, startTransition] = useTransition()
  const [results, setResults] = useState<SearchResult[]>([])

  const handleSearch = (value: string) => {
    // 立即:更新输入(紧急——用户正在输入)
    setQuery(value)

    // 延迟:执行昂贵搜索(非紧急——可以等待)
    startTransition(() => {
      const newResults = searchIndex.query(value)
      setResults(newResults)
    })
  }

  return (
    <div>
      <input value={query} onChange={e => handleSearch(e.target.value)} />
      {isPending && <Spinner />}
      <ResultsList results={results} />
    </div>
  )
}

useDeferredValue——当你无法控制状态更新时:

function SearchResults({ query }: { query: string }) {
  const deferredQuery = useDeferredValue(query)

  const results = useMemo(() => searchIndex.query(deferredQuery), [deferredQuery])

  return (
    <div style={{ opacity: query !== deferredQuery ? 0.7 : 1 }}>
      {results.map(r => <ResultItem key={r.id} result={r} />)}
    </div>
  )
}

虚拟列表:处理大量数据

渲染10,000个列表项会导致浏览器崩溃。虚拟列表只渲染可见部分:

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

function VirtualUserList({ users }: { users: User[] }) {
  const parentRef = useRef<HTMLDivElement>(null)

  const virtualizer = useVirtualizer({
    count: users.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 72,
    overscan: 5,
  })

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

使用虚拟化,渲染100,000个项与渲染20个一样快。

2026年React性能优化:memo、并发特性与性能分析插图

状态共置:架构修复

将状态提升过高会导致过多重新渲染:

// 错误:App中的搜索状态在每次按键时重新渲染整个树
function App() {
  const [searchQuery, setSearchQuery] = useState('')
  return (
    <div>
      <Header />   {/* 不必要地重新渲染 */}
      <Sidebar />  {/* 不必要地重新渲染 */}
      <SearchBar query={searchQuery} onChange={setSearchQuery} />
      <SearchResults query={searchQuery} />
    </div>
  )
}

// 正确:将状态与需要它的组件共置
function App() {
  return (
    <div>
      <Header />
      <Sidebar />
      <SearchSection /> {/* 包含自己的状态 */}
    </div>
  )
}

function SearchSection() {
  const [searchQuery, setSearchQuery] = useState('')
  return (
    <>
      <SearchBar query={searchQuery} onChange={setSearchQuery} />
      <SearchResults query={searchQuery} />
    </>
  )
}

Context性能陷阱

// 错误:当user或theme变化时,每个消费者都重新渲染
const AppContext = createContext<AppState>(defaultState)

// 正确:拆分context——user消费者不会因theme变化而重新渲染
const UserContext  = createContext<UserContextValue>(defaultUser)
const ThemeContext = createContext<ThemeContextValue>(defaultTheme)

性能检查清单

  1. 先分析——在React DevTools Profiler中识别慢组件
  2. 检查渲染频率——是否比预期更频繁地渲染?
  3. 检查渲染时长——此渲染本身是否缓慢?
  4. 对于高频渲染:使用React.memo + useCallback/useMemo保持稳定引用
  5. 对于慢渲染:虚拟化长列表,使用useMemo进行昂贵计算,使用useTransition处理非紧急更新
  6. 对于架构问题:共置状态,拆分context

先测量,识别具体瓶颈,然后应用针对性修复。