React Performance Optimization: Stop Re-rendering Everything
Web Development3 de diciembre de 202514 min de lectura0 vistas
ReactJavaScriptPerformanceOptimizationFrontendWeb Development
Compartir:
React Performance Optimization: Stop Re-rendering Everything
React is fast, but it can get slow if you're not careful. Let's learn how to keep your React apps lightning-fast with proper optimization techniques.
The Performance Problem
React re-renders components when:
- State changes
- Props change
- Parent component re-renders
The issue? Unnecessary re-renders waste CPU cycles and slow down your app.
React.memo: Prevent Unnecessary Re-renders
Without React.memo
// This re-renders every time parent re-renders function UserCard({ name, email }) { console.log("UserCard rendered"); return ( <div> <h3>{name}</h3> <p>{email}</p> </div> ); }
With React.memo
// Only re-renders when name or email changes const UserCard = React.memo(({ name, email }) => { console.log("UserCard rendered"); return ( <div> <h3>{name}</h3> <p>{email}</p> </div> ); });
Custom Comparison Function
const UserCard = React.memo( ({ user }) => { return ( <div> <h3>{user.name}</h3> <p>{user.email}</p> </div> ); }, (prevProps, nextProps) => { // Return true if props are equal (skip re-render) return prevProps.user.id === nextProps.user.id; } );
useMemo: Memoize Expensive Calculations
Without useMemo
function ProductList({ products }) { // Recalculates on every render! const sortedProducts = products .sort((a, b) => b.price - a.price) .filter(p => p.inStock); return ( <div> {sortedProducts.map(p => <ProductCard key={p.id} product={p} />)} </div> ); }
With useMemo
function ProductList({ products }) { // Only recalculates when products change const sortedProducts = useMemo(() => { console.log("Calculating sorted products"); return products .sort((a, b) => b.price - a.price) .filter(p => p.inStock); }, [products]); return ( <div> {sortedProducts.map(p => <ProductCard key={p.id} product={p} />)} </div> ); }
useCallback: Memoize Functions
The Problem
function Parent() { const [count, setCount] = useState(0); // New function created on every render const handleClick = () => { console.log("Clicked!"); }; return ( <> <button onClick={() => setCount(count + 1)}>Count: {count}</button> <Child onClick={handleClick} /> {/* Child re-renders unnecessarily */} </> ); } const Child = React.memo(({ onClick }) => { console.log("Child rendered"); return <button onClick={onClick}>Click Me</button>; });
The Solution
function Parent() { const [count, setCount] = useState(0); // Same function reference across renders const handleClick = useCallback(() => { console.log("Clicked!"); }, []); // Dependencies array return ( <> <button onClick={() => setCount(count + 1)}>Count: {count}</button> <Child onClick={handleClick} /> {/* Child doesn't re-render */} </> ); }
Code Splitting: Load What You Need
Route-Based Splitting
import { lazy, Suspense } from 'react'; // Instead of: import Dashboard from './Dashboard'; const Dashboard = lazy(() => import('./Dashboard')); const Profile = lazy(() => import('./Profile')); const Settings = lazy(() => import('./Settings')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/profile" element={<Profile />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Suspense> ); }
Component-Based Splitting
const HeavyChart = lazy(() => import('./HeavyChart')); function Dashboard() { const [showChart, setShowChart] = useState(false); return ( <div> <button onClick={() => setShowChart(true)}>Show Chart</button> {showChart && ( <Suspense fallback={<div>Loading chart...</div>}> <HeavyChart /> </Suspense> )} </div> ); }
Virtualization: Render What's Visible
Without Virtualization (Slow)
function UserList({ users }) { // Renders 10,000 DOM elements! return ( <div> {users.map(user => ( <UserCard key={user.id} user={user} /> ))} </div> ); }
With Virtualization (Fast)
import { FixedSizeList } from 'react-window'; function UserList({ users }) { // Only renders visible rows (~20 DOM elements) return ( <FixedSizeList height={600} itemCount={users.length} itemSize={80} width="100%" > {({ index, style }) => ( <div style={style}> <UserCard user={users[index]} /> </div> )} </FixedSizeList> ); }
Debouncing and Throttling
Debounce: Wait for User to Stop Typing
import { useState, useCallback } from 'react'; import debounce from 'lodash/debounce'; function SearchInput() { const [query, setQuery] = useState(''); // Only search 500ms after user stops typing const debouncedSearch = useCallback( debounce((value) => { console.log('Searching for:', value); // API call here }, 500), [] ); const handleChange = (e) => { setQuery(e.target.value); debouncedSearch(e.target.value); }; return <input value={query} onChange={handleChange} />; }
Throttle: Limit Function Calls
import { useCallback } from 'react'; import throttle from 'lodash/throttle'; function ScrollTracker() { // Only execute once every 200ms const handleScroll = useCallback( throttle(() => { console.log('Scroll position:', window.scrollY); }, 200), [] ); useEffect(() => { window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, [handleScroll]); return <div>Scroll me!</div>; }
Context API Optimization
The Problem
// Every component re-renders when any context value changes const AppContext = createContext(); function App() { const [user, setUser] = useState(null); const [theme, setTheme] = useState('light'); return ( <AppContext.Provider value={{ user, setUser, theme, setTheme }}> <Header /> <Main /> <Footer /> </AppContext.Provider> ); }
The Solution: Split Contexts
const UserContext = createContext(); const ThemeContext = createContext(); function App() { const [user, setUser] = useState(null); const [theme, setTheme] = useState('light'); return ( <UserContext.Provider value={{ user, setUser }}> <ThemeContext.Provider value={{ theme, setTheme }}> <Header /> <Main /> <Footer /> </ThemeContext.Provider> </UserContext.Provider> ); }
Image Optimization
// Lazy load images import { LazyLoadImage } from 'react-lazy-load-image-component'; function ProductImage({ src, alt }) { return ( <LazyLoadImage src={src} alt={alt} effect="blur" threshold={100} width={300} height={300} /> ); } // Use next/image for automatic optimization import Image from 'next/image'; function ProductImage({ src, alt }) { return ( <Image src={src} alt={alt} width={300} height={300} loading="lazy" placeholder="blur" /> ); }
Performance Checklist
- ✅ Use React.memo for expensive components
- ✅ Use useMemo for expensive calculations
- ✅ Use useCallback for function props
- ✅ Implement code splitting for routes
- ✅ Use virtualization for long lists
- ✅ Debounce/throttle event handlers
- ✅ Split Context into smaller contexts
- ✅ Lazy load images
- ✅ Use production build
- ✅ Profile with React DevTools
When NOT to Optimize
❌ Don't optimize prematurely ❌ Don't use useMemo for simple calculations ❌ Don't use React.memo for all components ❌ Don't over-split contexts
Rule of thumb: Optimize when you measure a performance problem, not before.
Measuring Performance
import { Profiler } from 'react'; function onRenderCallback( id, phase, actualDuration, baseDuration, startTime, commitTime ) { console.log(`${id} took ${actualDuration}ms`); } function App() { return ( <Profiler id="App" onRender={onRenderCallback}> <Dashboard /> </Profiler> ); }
Conclusion
React performance optimization is about finding the right balance:
- Profile first, optimize later
- Use built-in tools (memo, useMemo, useCallback)
- Split code intelligently
- Virtualize long lists
- Measure the impact
Remember: Fast apps = Happy users! 🚀⚡