Skip to content

React 性能优化技巧

React 应用性能优化是一个重要的话题。本文将介绍 React 性能优化的各种技巧和最佳实践,帮助你构建更快、更流畅的应用。

性能优化的核心原则

在开始优化之前,需要理解 React 的性能特点:

  • 虚拟 DOM:React 使用虚拟 DOM 来减少实际 DOM 操作
  • 协调算法:React 通过 diff 算法来决定需要更新的部分
  • 渲染机制:组件状态或 props 变化时会触发重新渲染

组件渲染优化

使用 React.memo

React.memo 是一个高阶组件,用于防止不必要的重新渲染。

javascript
import { memo } from 'react'

const UserCard = memo(function UserCard({ name, email }) {
  return (
    <div>
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  )
})

// 只有当 name 或 email 变化时才重新渲染
function UserList({ users }) {
  return (
    <div>
      {users.map(user => (
        <UserCard key={user.id} name={user.name} email={user.email} />
      ))}
    </div>
  )
}

自定义比较函数

javascript
const UserCard = memo(
  function UserCard({ user }) {
    return <div>{user.name}</div>
  },
  (prevProps, nextProps) => {
    // 返回 true 表示 props 相等,不需要重新渲染
    return prevProps.user.id === nextProps.user.id
  }
)

使用 useMemo 缓存计算结果

javascript
import { useMemo } from 'react'

function ExpensiveList({ items, filter }) {
  // 只有当 items 或 filter 变化时才重新计算
  const filteredItems = useMemo(() => {
    return items.filter(item => {
      return item.category === filter
    })
  }, [items, filter])
  
  return (
    <ul>
      {filteredItems.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}

使用 useCallback 缓存函数

javascript
import { useState, useCallback } from 'react'

function Parent() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('')
  
  // 缓存函数,避免子组件不必要的重新渲染
  const handleClick = useCallback(() => {
    console.log('点击了')
  }, []) // 空依赖数组,函数永远不会变化
  
  const handleNameChange = useCallback((newName) => {
    setName(newName)
  }, []) // 注意:这里 setName 是稳定的,不需要放在依赖中
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <Child onClick={handleClick} onNameChange={handleNameChange} />
    </div>
  )
}

const Child = memo(function Child({ onClick, onNameChange }) {
  return (
    <div>
      <button onClick={onClick}>子组件按钮</button>
      <input onChange={e => onNameChange(e.target.value)} />
    </div>
  )
})

列表渲染优化

使用 key 属性

正确的 key 可以帮助 React 识别哪些元素改变了。

javascript
// ✅ 正确:使用唯一且稳定的 ID
{items.map(item => (
  <Item key={item.id} data={item} />
))}

// ❌ 错误:使用索引作为 key(当列表会重新排序时)
{items.map((item, index) => (
  <Item key={index} data={item} />
))}

虚拟滚动

对于长列表,使用虚拟滚动只渲染可见部分。

javascript
import { FixedSizeList } from 'react-window'

function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  )
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  )
}

状态管理优化

状态提升和状态下沉

将状态放在最合适的位置,避免不必要的状态提升。

javascript
// ❌ 不好:状态提升过高
function App() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <Header count={count} />
      <Main count={count} setCount={setCount} />
      <Footer count={count} />
    </div>
  )
}

// ✅ 好:状态下沉到需要的地方
function App() {
  return (
    <div>
      <Header />
      <Main /> {/* 状态在 Main 内部管理 */}
      <Footer />
    </div>
  )
}

使用 useReducer 管理复杂状态

javascript
import { useReducer } from 'react'

const initialState = { count: 0, step: 1 }

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step }
    case 'decrement':
      return { ...state, count: state.count - state.step }
    case 'setStep':
      return { ...state, step: action.step }
    default:
      return state
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState)
  
  return (
    <div>
      <input
        value={state.step}
        onChange={e => dispatch({ type: 'setStep', step: Number(e.target.value) })}
      />
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  )
}

代码分割

React.lazy 和 Suspense

使用 React.lazy 进行代码分割,延迟加载组件。

javascript
import { lazy, Suspense } from 'react'

const LazyComponent = lazy(() => import('./LazyComponent'))

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <LazyComponent />
    </Suspense>
  )
}

路由级别的代码分割

javascript
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'

const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const Contact = lazy(() => import('./pages/Contact'))

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>加载中...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

副作用优化

useEffect 的依赖优化

javascript
import { useState, useEffect, useRef } from 'react'

function Timer() {
  const [count, setCount] = useState(0)
  const intervalRef = useRef(null)
  
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(prev => prev + 1) // 使用函数式更新,不需要 count 依赖
    }, 1000)
    
    return () => {
      clearInterval(intervalRef.current)
    }
  }, []) // 空依赖数组,只在挂载时执行
  
  return <div>计数: {count}</div>
}

避免在渲染中执行副作用

javascript
// ❌ 不好:在渲染中执行副作用
function Component({ userId }) {
  const [user, setUser] = useState(null)
  
  fetch(`/api/users/${userId}`).then(setUser) // 错误!
  
  return <div>{user?.name}</div>
}

// ✅ 好:在 useEffect 中执行
function Component({ userId }) {
  const [user, setUser] = useState(null)
  
  useEffect(() => {
    fetch(`/api/users/${userId}`).then(res => res.json()).then(setUser)
  }, [userId])
  
  return <div>{user?.name}</div>
}

性能监控

使用 React DevTools Profiler

React DevTools 提供了 Profiler 工具,可以分析组件的渲染性能。

使用 Performance API

javascript
function measurePerformance(componentName, fn) {
  const start = performance.now()
  fn()
  const end = performance.now()
  console.log(`${componentName} 耗时: ${end - start}ms`)
}

// 使用
measurePerformance('ExpensiveComponent', () => {
  // 组件渲染逻辑
})

最佳实践总结

  1. 合理使用 memo、useMemo、useCallback:不要过度使用,只在必要时使用
  2. 优化列表渲染:使用正确的 key,考虑虚拟滚动
  3. 代码分割:使用 React.lazy 进行路由和组件级别的代码分割
  4. 状态管理:将状态放在合适的位置,避免不必要的状态提升
  5. 监控性能:使用 React DevTools 和 Performance API 监控性能
  6. 避免过早优化:先确保代码正确,再考虑性能优化

总结

React 性能优化需要从多个方面入手:组件渲染、列表渲染、状态管理、代码分割等。通过合理使用 React 提供的优化工具和遵循最佳实践,可以显著提升应用的性能。记住,性能优化是一个持续的过程,需要根据实际情况进行调整。


辛田信息技术 · 内部技术分享 · 仅供学习与参考