DEV Community

Tianya School
Tianya School

Posted on

React Native Performance Optimization From Rendering to Networking

React Native performance optimization spans multiple areas, including rendering optimization, network request optimization, memory management, and platform-specific optimizations.

Rendering Optimization

  1. Avoid Unnecessary Renders: Use React.memo or PureComponent to prevent unnecessary component re-renders by comparing props and state.
import React, { memo } from 'react';

const MyComponent = memo(({ prop1, prop2 }) => {
  // Component logic
});

// Or use PureComponent
class MyComponent extends React.PureComponent {
  render() {
    // Component logic
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Use shouldComponentUpdate: For custom components, override shouldComponentUpdate to manually control rendering.
class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.prop1 !== this.props.prop1 || nextState.stateProp !== this.state.stateProp;
  }

  render() {
    // Component logic
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Use FlatList and SectionList: For long lists, use FlatList or SectionList to significantly improve performance through virtualization, rendering only items in the visible area.
import { FlatList } from 'react-native';

const MyList = ({ data }) => (
  <FlatList
    data={data}
    renderItem={({ item }) => <MyListItem item={item} />}
    keyExtractor={(item, index) => item.id.toString()}
  />
);
Enter fullscreen mode Exit fullscreen mode
  1. Reduce Style Computations: Avoid computing styles at runtime, especially within the render method.

  2. Lazy Loading and On-Demand Loading: For large components or images, use lazy loading to load them only when they approach the viewport.

Network Request Optimization

  1. Batch Requests: Whenever possible, combine multiple requests into one, such as using GraphQL or batch APIs.
  2. Caching: Implement caching mechanisms like Cache-Control or Service Workers to reduce redundant requests.
  3. Asynchronous and Preloading: Preload critical data at app startup or in the background.
  4. Error Handling and Retry Strategies: Implement intelligent error handling and retry logic to minimize failed requests.

Memory Management

  1. Clean Up References: Use the cleanup function in useEffect or the componentWillUnmount lifecycle method to clear subscriptions, timers, etc.
useEffect(() => {
  const subscription = someObservable.subscribe(data => {
    // Handle data
  });

  return () => {
    subscription.unsubscribe(); // Clean up reference
  };
}, []); // Run only once on component mount
Enter fullscreen mode Exit fullscreen mode
  1. Avoid Memory Leaks: Carefully check for unused objects and variables, ensuring they are properly released.
  2. Optimize Image Resources: Use compressed images and appropriate sizes to reduce memory usage.

Platform-Specific Optimization

  1. Use Native Modules: For performance-critical sections, write native modules to leverage platform-specific performance advantages.
  2. Optimize Animations: Use the Animated API or native animation libraries to improve animation performance.
  3. Leverage Platform Features: Optimize code based on platform characteristics, such as using UIRefreshControl on iOS instead of custom pull-to-refresh.
  4. Update React Native Version: Keep React Native updated to benefit from the latest performance improvements and fixes.

Code Splitting and Dynamic Imports

Code splitting reduces the initial bundle size, improving app startup speed. While React Native doesn’t natively support code splitting, services like react-native-code-push or expo-updates enable dynamic updates and on-demand loading.

// Using Expo Updates for dynamic module loading
import * as Updates from 'expo-updates';

async function loadDynamicModule() {
  const updateInfo = await Updates.fetchUpdateAsync();
  if (updateInfo.isAvailable) {
    await Updates.reloadAsync();
    // After reloading, dynamically import module from the new version
    const DynamicModule = await import('path/to/dynamic/module');
    // Use DynamicModule
  }
}
Enter fullscreen mode Exit fullscreen mode

Optimizing Images and Fonts

  • Compress Images: Use tools like TinyPNG or ImageOptim to compress images, reducing file size.
  • Use Vector Graphics: For icons and simple graphics, use SVG format for scalability and smaller file sizes.
  • Font Subsetting: Load only the font characters used in the app to reduce font file size.

Reduce Component Hierarchy

Minimize the depth of the component tree, as rendering traverses the entire tree. Optimize by composing components and removing unnecessary wrapper components.

Optimize FlatList Properties

FlatList offers optimization properties like initialNumToRender and maxToRenderPerBatch to reduce initial rendering overhead.

<FlatList
  data={data}
  renderItem={renderItem}
  keyExtractor={keyExtractor}
  initialNumToRender={10} // Number of items to render initially
  maxToRenderPerBatch={5} // Number of items to render per batch during scrolling
/>
Enter fullscreen mode Exit fullscreen mode

Avoid Higher-Order Components (HOCs)

HOCs can increase component complexity, leading to extra renders. Prefer function components and Hooks when possible.

Use Profiler Tools

React Native provides a built-in Profiler tool to identify performance bottlenecks. In development mode, open the developer menu, select "Profiler," and start recording to analyze which components take the most time to render.

Use Performance Monitoring Tools

Use libraries like react-native-perf-monitor or react-native-performance to monitor performance metrics in real-time, such as FPS and memory usage.

Optimize Network Libraries

If using a custom network library, ensure it’s optimized. For example, using axios instead of fetch may offer better performance due to more control and optimization options.

Use the Hermes Engine

Starting with React Native 0.60, Hermes is an optional JavaScript engine that provides faster startup times and better runtime performance. Ensure Hermes is enabled in your project.

Optimize Layouts

Avoid complex layouts like absolute positioning and negative margins. Use Flexbox or Grid layouts, which generally perform better.

Continuous Monitoring and Optimization

Performance optimization is an ongoing process. Regularly monitor app performance, using user feedback and data analytics to identify and address issues.

Avoid Unnecessary Global State

Global state (e.g., Redux or MobX) simplifies app management but can cause unnecessary renders. Use global state only when necessary to avoid over-reliance.

// Redux example
import { Provider } from 'react-redux';
import store from './store';

function App() {
  return (
    <Provider store={store}>
      <RootNavigator />
    </Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Use Pure Function Components and Hooks

Pure function components and Hooks (e.g., useState, useMemo) reduce unnecessary renders by recalculating only when dependencies change.

import React, { useState, useMemo } from 'react';

function MyComponent({ data }) {
  const [count, setCount] = useState(0);

  const computedValue = useMemo(() => {
    // Computation logic
    return data.length + count;
  }, [data, count]);

  return (
    <View>
      <Text>{computedValue}</Text>
      <Button onPress={() => setCount(count + 1)} title="Increment" />
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Use useCallback and useMemo

For functions passed to child components, use useCallback to cache function instances, avoiding new function creation on each render. For complex computations dependent on other values, use useMemo to prevent redundant calculations.

import React, { useCallback, useMemo } from 'react';

function ParentComponent({ items }) {
  const handleItemClick = useCallback(item => {
    console.log(`Clicked on ${item.name}`);
  }, []);

  const sortedItems = useMemo(() => {
    return items.sort((a, b) => a.name.localeCompare(b.name));
  }, [items]);

  return (
    <ChildComponent items={sortedItems} onItemClick={handleItemClick} />
  );
}
Enter fullscreen mode Exit fullscreen mode

Use useLayoutEffect

For side effects that need to run after layout completion, such as measuring element dimensions, use useLayoutEffect instead of useEffect. useLayoutEffect runs after all DOM changes are complete and before the screen updates.

import React, { useLayoutEffect, useRef } from 'react';

function MyComponent() {
  const textRef = useRef(null);

  useLayoutEffect(() => {
    const textWidth = textRef.current.measureLayout(findNodeHandle(textRef.current.parentNode));
    console.log('Text width:', textWidth);
  }, []);

  return <Text ref={textRef}>Hello, World!</Text>;
}
Enter fullscreen mode Exit fullscreen mode

Avoid Complex Operations in Event Handlers

Keep event handlers lightweight, avoiding complex computations or asynchronous operations. Move such operations to separate functions called after the event handler.

function MyComponent() {
  const handleClick = () => {
    // Lightweight operations, like setting state
    setIsLoading(true);

    // Move complex operations to a separate function
    fetchData().then(() => setIsLoading(false));
  };

  return (
    <TouchableOpacity onPress={handleClick}>
      <Text>Loading...</Text>
    </TouchableOpacity>
  );
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)