React 性能优化的思路

#react

目录

1. 总结

  • 记得使用 React Devtools 分析性能
  • ssr 首屏
  • 渲染
    • 缓存
    • 避免重复渲染
    • 列表 key
    • 虚拟列表
  • IO
    • 代码分割
    • 懒加载
    • 预加载
  • 长任务优化

2. 组件渲染优化

2.1. 使用 React.memo 避免不必要的重渲染

// 使用 React.memo 包装函数组件
const MyComponent = React.memo(function MyComponent(props) {
  /* 渲染逻辑 */
  return (
    // ...
  );
}, (prevProps, nextProps) => {
  // 可选的比较函数,返回 true 则不重新渲染
  return prevProps.id === nextProps.id;
});

// 对于类组件,可以使用 PureComponent
class MyPureComponent extends React.PureComponent {
  render() {
    return (
      // ...
    );
  }
}

2.2. 使用 useMemo 缓存计算结果

function ExpensiveComponent({ data }) {
  // 缓存计算结果
  const processedData = useMemo(() => {
    return data.map(item => expensiveOperation(item));
  }, [data]); // 只在 data 改变时重新计算

  return (
    <div>
      {processedData.map(item => (
        <div key={item.id}>{item.value}</div>
      ))}
    </div>
  );
}

2.3. 使用 useCallback 缓存函数

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

  // 缓存回调函数
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []); // 空依赖数组,函数永远不会改变

  return <ChildComponent onClick={handleClick} />;
}

3. 列表渲染优化

3.1. 使用合适的 key

// ❌ 错误示例:使用索引作为 key
{items.map((item, index) => (
  <ListItem key={index} item={item} />
))}

// ✅ 正确示例:使用唯一标识符作为 key
{items.map(item => (
  <ListItem key={item.id} item={item} />
))}

3.2. 虚拟列表

使用 react-windowreact-virtualized 处理长列表:

import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={400}
      width={300}
      itemCount={items.length}
      itemSize={35}
    >
      {Row}
    </FixedSizeList>
  );
}

4. 状态管理优化

4.1. 合理拆分状态

// ❌ 错误示例:所有状态放在一起
const [state, setState] = useState({
  user: null,
  posts: [],
  comments: [],
  settings: {}
});

// ✅ 正确示例:拆分状态
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [settings, setSettings] = useState({});

4.2. 使用 useReducer 管理复杂状态

const initialState = { count: 0 };

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

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}

5. 代码分割和懒加载

5.1. 使用 React.lazy 和 Suspense

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <Suspense fallback={<Loading />}>
      <OtherComponent />
    </Suspense>
  );
}

5.2. 路由级别的代码分割

import { BrowserRouter, Routes, Route } from 'react-router-dom';
import React, { Suspense } from 'react';

const Home = React.lazy(() => import('./routes/Home'));
const About = React.lazy(() => import('./routes/About'));
const Contact = React.lazy(() => import('./routes/Contact'));

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

6. 避免重复渲染

6.1. 使用 CSS 代替 JS 动画

// ❌ 使用 JS 动画
function AnimatedComponent() {
  const [position, setPosition] = useState(0);
  
  useEffect(() => {
    const animation = setInterval(() => {
      setPosition(p => p + 1);
    }, 16);
    return () => clearInterval(animation);
  }, []);

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

// ✅ 使用 CSS 动画
const AnimatedComponent = styled.div`
  animation: slide 1s linear infinite;
  
  @keyframes slide {
    from { transform: translateX(0); }
    to { transform: translateX(100px); }
  }
`;

6.2. 使用防抖和节流

import { debounce, throttle } from 'lodash';

function SearchComponent() {
  // 使用防抖处理搜索
  const debouncedSearch = useCallback(
    debounce((query) => {
      // 执行搜索
      performSearch(query);
    }, 300),
    []
  );

  // 使用节流处理滚动
  const throttledScroll = useCallback(
    throttle((event) => {
      // 处理滚动
      handleScroll(event);
    }, 100),
    []
  );

  useEffect(() => {
    window.addEventListener('scroll', throttledScroll);
    return () => window.removeEventListener('scroll', throttledScroll);
  }, [throttledScroll]);

  return (
    <input
      type="text"
      onChange={(e) => debouncedSearch(e.target.value)}
    />
  );
}

7. 优化资源加载

7.1. 图片懒加载

function ImageComponent({ src, alt }) {
  const imgRef = useRef();

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            imgRef.current.src = src;
            observer.disconnect();
          }
        });
      },
      { threshold: 0.1 }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, [src]);

  return <img ref={imgRef} alt={alt} />;
}

7.2. 预加载关键资源

function App() {
  useEffect(() => {
    // 预加载重要图片
    const preloadImages = ['/logo.png', '/hero.jpg'];
    preloadImages.forEach(src => {
      const img = new Image();
      img.src = src;
    });

    // 预加载其他组件
    const preloadComponent = () => import('./HeavyComponent');
  }, []);

  return (
    // ...
  );
}

8. 工具和监控

8.1. 使用 React DevTools 进行性能分析

// 开发环境中使用 Profiler
import { Profiler } from 'react';

function onRenderCallback(
  id, // 发生提交的 Profiler 树的 "id"
  phase, // "mount" (首次渲染)或 "update" (重新渲染)
  actualDuration, // 本次更新在渲染完成之前耗费的时间
  baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间
  startTime, // 本次更新开始渲染的时间
  commitTime, // 本次更新被提交的时间
  interactions // 属于本次更新的 interactions 的集合
) {
  // 记录或上报性能数据
  console.log(`${id} 渲染耗时: ${actualDuration}`);
}

function MyApp() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <App />
    </Profiler>
  );
}

9. 其他优化建议

  • 合理使用 Context

    • 避免将频繁变化的值放在 Context 中
    • 考虑拆分 Context 以减少不必要的重渲染
  • 使用 Web Workers 处理复杂计算

    const worker = new Worker('worker.js');
    
    worker.postMessage({ data: complexData });
    worker.onmessage = (event) => {
      setResult(event.data);
    };
    
  • 优化依赖包大小

    • 使用 import 语法进行按需加载
    • 使用较小的替代包
    • 定期审查和更新依赖
  • 服务端渲染 (SSR)

    • 考虑使用 Next.js 或其他 SSR 框架
    • 实现首屏快速加载
    • 优化 SEO
  • 使用性能监控工具

    • React DevTools
    • Chrome Performance 面板
    • Lighthouse
    • 自定义性能指标监控