Tối Ưu Hiệu Suất React Native 2026: Khởi Động, Render Danh Sách Và Animation

Hướng dẫn toàn diện tối ưu hiệu suất React Native 2026 với code mẫu thực tế: từ Hermes v1, memo/useCallback, FlashList v2 vs LegendList, expo-image, Reanimated 4 animation đến profiling và phát hiện nút thắt cổ chai.

Giới thiệu: Tại sao hiệu suất vẫn là yếu tố sống còn năm 2026?

React Native đã phát triển vượt bậc với Kiến Trúc Mới (Fabric, TurboModules, Bridgeless), Hermes v1 và Expo SDK 55. Nhưng thật lòng mà nói, dù framework có mạnh mẽ đến đâu, hiệu suất ứng dụng vẫn phụ thuộc vào cách bạn viết code. Một ứng dụng khởi động chậm 3 giây sẽ mất 53% người dùng — con số đó không phải đùa đâu. Danh sách cuộn giật lag khiến người dùng cảm thấy ứng dụng "rẻ tiền". Và animation không mượt ở 60 FPS? Trải nghiệm sẽ bị phá hỏng hoàn toàn.

Trong bài viết này, mình sẽ đi từ gốc đến ngọn: tối ưu thời gian khởi động, kỹ thuật tránh re-render không cần thiết, so sánh FlatList vs FlashList v2 vs LegendList, tối ưu hình ảnh, animation với Reanimated 4, và cuối cùng là profiling để tìm ra nút thắt cổ chai. Tất cả đều kèm code mẫu thực tế bạn có thể áp dụng ngay.

Phần 1: Tối ưu thời gian khởi động ứng dụng

Thời gian khởi động là ấn tượng đầu tiên của người dùng với ứng dụng. Bạn cần hiểu hai khái niệm:

  • Cold Start: Ứng dụng khởi chạy từ đầu, không có tiến trình nào trong bộ nhớ. Đây là trường hợp chậm nhất.
  • Warm Start: Ứng dụng vẫn còn trong bộ nhớ, chỉ cần đưa lên foreground.

Mục tiêu? Giảm Cold Start xuống dưới 2 giây. Nghe đơn giản nhưng để đạt được thì cần chiến lược cụ thể.

1.1 Tận dụng Hermes v1 với biên dịch AOT

Hermes v1 ra mắt cùng React Native 0.82, giờ đã là tiêu chuẩn trong SDK 55. Nó sử dụng biên dịch Ahead-of-Time (AOT) để chuyển JavaScript thành bytecode tối ưu ngay lúc build, loại bỏ hoàn toàn bước parse và compile khi app khởi động.

Theo kinh nghiệm thực tế, Hermes v1 mang lại cải thiện 30-50% thời gian cold start so với JavaScriptCore, đặc biệt rõ rệt trên thiết bị Android tầm trung. Tin vui là bạn không cần cấu hình gì thêm — Hermes đã bật mặc định:

// metro.config.js - Hermes đã được bật mặc định từ SDK 55
// Bạn có thể kiểm tra bằng cách log trong app
console.log(
  'Hermes enabled:',
  typeof HermesInternal !== 'undefined'
);
// Output: Hermes enabled: true

1.2 Lazy Loading và Code Splitting theo Route

Đây là một trong những kỹ thuật mình thấy hiệu quả nhất. Thay vì tải toàn bộ ứng dụng khi khởi động, bạn chỉ tải những gì người dùng cần ngay lập tức. React.lazy kết hợp Suspense làm việc này rất gọn:

import React, { Suspense, lazy } from 'react';
import { ActivityIndicator, View } from 'react-native';

// Thay vì import trực tiếp tất cả các màn hình
// import ProfileScreen from './screens/ProfileScreen';
// import SettingsScreen from './screens/SettingsScreen';

// Chỉ tải khi người dùng điều hướng đến
const ProfileScreen = lazy(() => import('./screens/ProfileScreen'));
const SettingsScreen = lazy(() => import('./screens/SettingsScreen'));

function LoadingFallback() {
  return (
    
      
    
  );
}

// Sử dụng trong Navigator
function AppNavigator() {
  return (
    
      
      
        {() => (
          }>
            
          
        )}
      
      
        {() => (
          }>
            
          
        )}
      
    
  );
}

1.3 Tối ưu import và giảm kích thước bundle

Lỗi này phổ biến đến mức đáng ngạc nhiên: import toàn bộ thư viện khi chỉ cần một hàm nhỏ. Kích thước bundle phình to vô cớ:

// ❌ Sai: Import toàn bộ lodash (khoảng 70KB)
import _ from 'lodash';
const result = _.debounce(handleSearch, 300);

// ✅ Đúng: Chỉ import hàm cần dùng (khoảng 2KB)
import debounce from 'lodash/debounce';
const result = debounce(handleSearch, 300);

// ❌ Sai: Import toàn bộ date-fns
import * as dateFns from 'date-fns';

// ✅ Đúng: Import hàm cụ thể
import { format, parseISO } from 'date-fns';

Muốn biết bundle của bạn đang "nặng" ở đâu? Dùng react-native-bundle-visualizer:

# Cài đặt
npx react-native-bundle-visualizer

# Hoặc dùng source-map-explorer
npx source-map-explorer android/app/build/generated/sourcemaps/react/release/index.android.bundle.map

1.4 Giảm khởi tạo module khi khởi động

Một trick khá hữu ích: dùng require() thay vì import cho các module nặng mà app không cần ngay lúc khởi động:

// ❌ Import ngay lập tức - tốn thời gian khởi động
import { Camera } from 'expo-camera';
import { MapView } from 'react-native-maps';
import Analytics from '@segment/analytics-react-native';

// ✅ Tải khi thực sự cần - tiết kiệm thời gian khởi động
function CameraScreen() {
  const [CameraModule, setCameraModule] = useState(null);

  useEffect(() => {
    // Tải module Camera chỉ khi user mở màn hình này
    const loadCamera = async () => {
      const { Camera } = require('expo-camera');
      setCameraModule(() => Camera);
    };
    loadCamera();
  }, []);

  if (!CameraModule) return ;
  return ;
}

Phần 2: Ngăn chặn Re-render không cần thiết

Re-render không cần thiết là "kẻ giết người thầm lặng" về hiệu suất. Khi một component re-render, toàn bộ cây component con cũng render theo. Trong app lớn, chuyện này có thể gây ra hàng trăm lần render dư thừa mỗi giây mà bạn không hề hay biết.

2.1 React.memo - Bảo vệ component khỏi re-render

React.memo là Higher-Order Component giúp component chỉ re-render khi props thực sự thay đổi. Đơn giản nhưng cực kỳ hiệu quả:

import React, { memo } from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';

// ❌ Không dùng memo - re-render mỗi khi parent render
function UserCard({ name, avatar, bio }) {
  console.log('UserCard rendered'); // Sẽ log rất nhiều lần
  return (
    
      
      {name}
      {bio}
    
  );
}

// ✅ Dùng memo - chỉ re-render khi name, avatar hoặc bio thay đổi
const UserCardMemo = memo(function UserCard({ name, avatar, bio }) {
  console.log('UserCard rendered'); // Chỉ log khi props thay đổi
  return (
    
      
      {name}
      {bio}
    
  );
});

// Với custom comparator cho các trường hợp phức tạp
const UserCardCustom = memo(UserCard, (prevProps, nextProps) => {
  // Return true = KHÔNG re-render, false = re-render
  return prevProps.name === nextProps.name
    && prevProps.avatar === nextProps.avatar;
  // Bỏ qua thay đổi bio để tối ưu
});

2.2 useCallback và useMemo - Ổn định tham chiếu

Đây là cái bẫy mà nhiều dev mắc phải: truyền function hoặc object làm props, mỗi lần parent render tạo ra tham chiếu mới, khiến React.memo mất tác dụng. useCallbackuseMemo giải quyết chuyện này:

import React, { useState, useCallback, useMemo, memo } from 'react';
import { View, TextInput, FlatList, Text, Pressable } from 'react-native';

const TodoItem = memo(function TodoItem({ item, onToggle, onDelete }) {
  console.log('TodoItem rendered:', item.id);
  return (
     onToggle(item.id)}>
      
        
          {item.text}
        
         onDelete(item.id)}>
          Xóa
        
      
    
  );
});

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState('all');

  // ✅ useCallback: giữ ổn định tham chiếu hàm
  const handleToggle = useCallback((id) => {
    setTodos(prev =>
      prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
    );
  }, []);

  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(t => t.id !== id));
  }, []);

  // ✅ useMemo: chỉ tính toán lại khi todos hoặc filter thay đổi
  const filteredTodos = useMemo(() => {
    switch (filter) {
      case 'active': return todos.filter(t => !t.done);
      case 'done': return todos.filter(t => t.done);
      default: return todos;
    }
  }, [todos, filter]);

  // ✅ useMemo cho statistics
  const stats = useMemo(() => ({
    total: todos.length,
    done: todos.filter(t => t.done).length,
    active: todos.filter(t => !t.done).length,
  }), [todos]);

  const renderItem = useCallback(({ item }) => (
    
  ), [handleToggle, handleDelete]);

  return (
    
      Tổng: {stats.total} | Hoàn thành: {stats.done}
       item.id}
      />
    
  );
}

2.3 React Compiler - Tương lai không cần memo thủ công

Đây là tin vui cho năm 2026: React Compiler đang dần trở thành công cụ tiêu chuẩn. Compiler tự động phân tích code và thêm memoization ở những nơi cần thiết, giúp bạn không phải dùng React.memo, useMemouseCallback thủ công nữa.

// Với React Compiler, bạn chỉ cần viết code tự nhiên
// Compiler sẽ tự động tối ưu

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState('all');

  // Compiler tự nhận diện và memoize
  const filteredTodos = todos.filter(t => {
    if (filter === 'active') return !t.done;
    if (filter === 'done') return t.done;
    return true;
  });

  // Compiler tự thêm useCallback ở đây
  const handleToggle = (id) => {
    setTodos(prev =>
      prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
    );
  };

  return (
     (
        
      )}
    />
  );
}

Tuy nhiên, thời điểm hiện tại React Compiler vẫn chưa hoàn toàn ổn định cho production trên React Native. Nên bạn vẫn cần nắm vững các kỹ thuật memo thủ công — ít nhất là trong thời gian tới.

Phần 3: Render danh sách hiệu quả — FlatList vs FlashList v2 vs LegendList

Nói thật, render danh sách lớn là một trong những thách thức "đau đầu" nhất khi làm React Native. Cùng so sánh ba giải pháp chính năm 2026 nhé.

3.1 FlatList - Giải pháp built-in

FlatList là component mặc định, hoạt động ổn cho danh sách vừa và nhỏ (dưới 100 item). Nhưng khi danh sách lớn lên, bạn sẽ bắt đầu thấy vấn đề. Đây là cách tối ưu nó tốt nhất có thể:

import { FlatList } from 'react-native';

function OptimizedFlatList({ data }) {
  const renderItem = useCallback(({ item }) => (
    
  ), []);

  // ✅ Các props tối ưu quan trọng
  return (
     item.id}

      // getItemLayout: bỏ qua bước đo kích thước
      // CHỈ dùng khi tất cả item cùng chiều cao
      getItemLayout={(data, index) => ({
        length: ITEM_HEIGHT,
        offset: ITEM_HEIGHT * index,
        index,
      })}

      // Số item render ban đầu - đủ để phủ màn hình
      initialNumToRender={10}

      // Giảm windowSize từ mặc định 21 xuống 5 để tiết kiệm bộ nhớ
      windowSize={5}

      // Số item mount tối đa cùng lúc
      maxToRenderPerBatch={10}

      // Độ trễ giữa mỗi batch render (ms)
      updateCellsBatchingPeriod={50}

      // Xóa item không hiển thị khỏi bộ nhớ
      removeClippedSubviews={true}
    />
  );
}

3.2 FlashList v2 - Giải pháp từ Shopify

FlashList v2 là bản viết lại hoàn toàn mà Shopify phát hành giữa năm 2025, thiết kế riêng cho Fabric Architecture. Điểm khác biệt cốt lõi là FlashList tái sử dụng component thay vì tạo mới — tương tự RecyclerView trên Android. Và thật sự, sự khác biệt hiệu suất khá ấn tượng:

import { FlashList } from '@shopify/flash-list';

function OptimizedFlashList({ data }) {
  const renderItem = useCallback(({ item }) => (
    
  ), []);

  return (
     item.id}

      // FlashList v2 không cần estimatedItemSize nữa!
      // Layout được tính đồng bộ và chính xác

      // Số item render ban đầu
      estimatedFirstItemOffset={0}

      // Callback khi list đang trống
      ListEmptyComponent={}
    />
  );
}

// So sánh hiệu suất thực tế:
// Danh sách 10,000 item trên Android tầm trung:
// - FlatList: ~45 FPS khi cuộn nhanh, mount time ~800ms
// - FlashList v2: ~58 FPS khi cuộn nhanh, mount time ~200ms

3.3 LegendList - 100% JavaScript, không cần native module

LegendList đến từ LegendApp, viết hoàn toàn bằng JavaScript. Điểm mạnh lớn nhất? Hỗ trợ chiều cao item động thực sự, cuộn hai chiều, và đặc biệt là giao diện chat không cần dùng hack:

import { LegendList } from '@legendapp/list';

function ChatScreen({ messages }) {
  const renderItem = useCallback(({ item }) => (
    
  ), []);

  return (
     item.id}

      // ✅ Hỗ trợ chat layout native - không cần inverted hack
      alignItemsAtEnd={true}

      // ✅ Chiều cao item động thực sự
      estimatedItemSize={80}

      // ✅ Cuộn hai chiều cho infinite scroll
      onStartReached={loadOlderMessages}
      onEndReached={loadNewerMessages}

      // Giữ vị trí cuộn khi thêm item mới ở đầu
      maintainScrollAtEnd={true}
    />
  );
}

3.4 Vậy chọn cái nào?

Câu hỏi kinh điển. Câu trả lời phụ thuộc vào use case của bạn:

  • FlatList: Danh sách đơn giản dưới 100 item, không muốn thêm dependency, item chiều cao cố định. Vẫn là lựa chọn an toàn cho hầu hết trường hợp.
  • FlashList v2: Danh sách lớn (hàng nghìn item), cần hiệu suất cuộn tối đa, masonry layout, hoặc app thương mại điện tử. Được Shopify hậu thuẫn nên khá ổn định.
  • LegendList: Giao diện chat, item chiều cao động phức tạp, cuộn hai chiều, hoặc khi muốn tránh native module linking (hữu ích với Expo Go).

Phần 4: Tối ưu hình ảnh

Hình ảnh thường là thủ phạm hàng đầu gây chậm app và ngốn bộ nhớ. Mình đã từng debug một app mà bộ nhớ tăng vọt chỉ vì load ảnh không tối ưu. Dưới đây là những gì bạn cần làm.

4.1 Dùng expo-image thay cho Image mặc định

Component Image mặc định thiếu caching tốt và nhiều tính năng tối ưu. expo-image là giải pháp thay thế được khuyến nghị — và thật sự nó tốt hơn rất nhiều:

import { Image } from 'expo-image';

// ✅ expo-image với đầy đủ tối ưu
function ProductImage({ uri, width, height }) {
  return (
    
  );
}

// ✅ Preload ảnh quan trọng
import { Image } from 'expo-image';

async function preloadCriticalImages() {
  await Image.prefetch([
    'https://example.com/hero-banner.webp',
    'https://example.com/logo.webp',
  ]);
}

4.2 WebP và resize phía server

WebP cho chất lượng tương đương JPEG nhưng nhỏ hơn 25-35%. Kết hợp resize phía server để chỉ tải đúng kích thước cần thiết — đừng bắt điện thoại phải xử lý ảnh 4K chỉ để hiển thị thumbnail 80px:

// ✅ Tạo URL ảnh với kích thước phù hợp
function getOptimizedImageUrl(originalUrl, { width, height, quality = 80 }) {
  // Ví dụ với Cloudinary
  const cloudinaryBase = 'https://res.cloudinary.com/your-cloud/image/upload';
  const transforms = `w_${width},h_${height},q_${quality},f_webp,c_fill`;
  return `${cloudinaryBase}/${transforms}/${originalUrl}`;
}

// Sử dụng trong component
function ResponsiveImage({ imageId }) {
  const { width: screenWidth } = useWindowDimensions();
  const imageWidth = screenWidth - 32; // padding 16 mỗi bên
  const imageHeight = Math.round(imageWidth * 0.6); // tỉ lệ 5:3

  const optimizedUri = getOptimizedImageUrl(imageId, {
    width: Math.round(imageWidth * PixelRatio.get()), // Nhân pixel ratio
    height: Math.round(imageHeight * PixelRatio.get()),
    quality: 75,
  });

  return (
    
  );
}

4.3 Tối ưu ảnh trong danh sách

Hiển thị ảnh trong FlatList hoặc FlashList cần thêm vài chú ý:

// ✅ Component ảnh tối ưu cho danh sách
const ListImage = memo(function ListImage({ uri, size = 80 }) {
  return (
    
  );
});

// Sử dụng trong FlatList/FlashList
const renderItem = useCallback(({ item }) => (
  
    
    {item.title}
  
), []);

Phần 5: Animation hiệu suất cao với Reanimated 4

Animation mượt ở 60 FPS là thứ tạo nên khác biệt giữa app "tốt" và "tuyệt vời". Reanimated 4, phát hành ổn định đầu 2026, mang đến hai hệ thống: CSS Animations mới và Worklets truyền thống. Cả hai đều chạy trên UI thread, nên không lo bị block bởi JS thread.

5.1 CSS Animations - Đơn giản mà hiệu suất cao

Reanimated 4 cho phép dùng cú pháp CSS quen thuộc để tạo animation. Nếu bạn từng làm web thì sẽ thấy rất thân thiện:

import Animated, {
  useAnimatedStyle,
  CSSTransition,
  CSSAnimation,
} from 'react-native-reanimated';

// ✅ CSS Transition - chuyển đổi mượt giữa các trạng thái
function AnimatedCard({ isExpanded }) {
  const animatedStyle = useAnimatedStyle(() => ({
    height: isExpanded ? 300 : 80,
    opacity: isExpanded ? 1 : 0.8,
    transition: {
      height: { duration: 300, timingFunction: 'ease-in-out' },
      opacity: { duration: 200 },
    },
  }));

  return (
    
      {/* Nội dung card */}
    
  );
}

// ✅ CSS Keyframe Animation
function PulsingDot() {
  const animatedStyle = useAnimatedStyle(() => ({
    animation: {
      name: {
        from: { transform: [{ scale: 1 }], opacity: 1 },
        '50%': { transform: [{ scale: 1.3 }], opacity: 0.7 },
        to: { transform: [{ scale: 1 }], opacity: 1 },
      },
      duration: 1500,
      iterationCount: 'infinite',
      timingFunction: 'ease-in-out',
    },
  }));

  return (
    
  );
}

5.2 Worklets - Cho animation phức tạp và gesture-driven

Khi animation phụ thuộc vào cử chỉ người dùng hoặc cần logic phức tạp, Worklets vẫn là vua. Đây là ví dụ SwipeableCard mà mình rất hay dùng:

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withTiming,
  interpolate,
  runOnUI,
} from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

function SwipeableCard({ onSwipeLeft, onSwipeRight }) {
  const translateX = useSharedValue(0);
  const rotateZ = useSharedValue(0);

  const gesture = Gesture.Pan()
    .onUpdate((event) => {
      // ✅ Chạy trên UI thread - không lag
      translateX.value = event.translationX;
      rotateZ.value = interpolate(
        event.translationX,
        [-200, 0, 200],
        [-15, 0, 15]
      );
    })
    .onEnd((event) => {
      if (Math.abs(event.translationX) > 150) {
        // Swipe đủ xa - thực hiện action
        translateX.value = withTiming(
          event.translationX > 0 ? 500 : -500,
          { duration: 200 },
          () => {
            runOnUI(() => {
              if (event.translationX > 0) {
                onSwipeRight?.();
              } else {
                onSwipeLeft?.();
              }
            })();
          }
        );
      } else {
        // Không đủ xa - quay về vị trí ban đầu
        translateX.value = withSpring(0, {
          damping: 15,
          stiffness: 150,
        });
        rotateZ.value = withSpring(0);
      }
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { translateX: translateX.value },
      { rotateZ: `${rotateZ.value}deg` },
    ],
  }));

  return (
    
      
        {/* Nội dung card */}
      
    
  );
}

5.3 Quy tắc vàng cho animation hiệu suất

Sau nhiều dự án, mình đúc kết được mấy quy tắc này — vi phạm bất kỳ điều nào đều có thể khiến animation giật:

  • Luôn dùng useNativeDriver: true nếu dùng Animated API mặc định. Hoặc tốt hơn, chuyển sang Reanimated luôn.
  • Giới hạn số component animation: khoảng 100 trên Android tầm thấp, 500 trên iOS.
  • Tránh animate layout properties như width, height, padding, margin — chúng trigger layout recalculation. Ưu tiên transformopacity.
  • Không đọc shared values trên JS thread: chỉ đọc trong worklets trên UI thread.
  • Dùng cancelAnimation trong cleanup useEffect để tránh memory leak.
// ❌ Sai: Animate width gây layout recalculation
const badStyle = useAnimatedStyle(() => ({
  width: animatedWidth.value,  // Chậm - trigger layout
  height: animatedHeight.value, // Chậm
}));

// ✅ Đúng: Dùng transform thay thế
const goodStyle = useAnimatedStyle(() => ({
  transform: [
    { scaleX: animatedScaleX.value },  // Nhanh - không trigger layout
    { scaleY: animatedScaleY.value },  // Nhanh
  ],
  opacity: animatedOpacity.value,  // Nhanh
}));

Phần 6: Quản lý bộ nhớ và tránh Memory Leak

Memory leak trên mobile nghiêm trọng hơn web rất nhiều. Hệ điều hành sẽ kill app khi bộ nhớ vượt ngưỡng, gây crash đột ngột. Người dùng không biết tại sao — họ chỉ biết app "dở".

6.1 Dọn dẹp Effect đúng cách

Rule số 1: mọi subscription, timer, listener đều phải được cleanup khi component unmount.

function LocationTracker() {
  const [location, setLocation] = useState(null);

  useEffect(() => {
    let isMounted = true;
    let subscription = null;

    const startTracking = async () => {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted' || !isMounted) return;

      subscription = await Location.watchPositionAsync(
        {
          accuracy: Location.Accuracy.High,
          distanceInterval: 10,
        },
        (newLocation) => {
          if (isMounted) {
            setLocation(newLocation);
          }
        }
      );
    };

    startTracking();

    // ✅ Cleanup: hủy subscription và đánh dấu unmounted
    return () => {
      isMounted = false;
      if (subscription) {
        subscription.remove();
      }
    };
  }, []);

6.2 Hủy request HTTP khi component unmount

Cái này bị bỏ qua nhiều lắm. Request HTTP vẫn chạy dù component đã unmount, và khi response về nó cố gọi setState trên component đã "chết":

function SearchScreen() {
  const [results, setResults] = useState([]);

  const search = useCallback(async (query) => {
    // ✅ Sử dụng AbortController để hủy request
    const controller = new AbortController();

    try {
      const response = await fetch(
        `https://api.example.com/search?q=${query}`,
        { signal: controller.signal }
      );
      const data = await response.json();
      setResults(data.items);
    } catch (error) {
      if (error.name !== 'AbortError') {
        console.error('Search failed:', error);
      }
    }

    // Trả về hàm cleanup
    return () => controller.abort();
  }, []);

  useEffect(() => {
    const cleanup = search(debouncedQuery);
    return () => cleanup?.then?.(fn => fn?.());
  }, [debouncedQuery, search]);

  return ;
}

6.3 Tránh giữ reference lớn trong closure

Lỗi này tinh vi hơn mà ít ai để ý:

// ❌ Sai: Closure giữ reference đến toàn bộ largeData
function DataProcessor({ largeData }) {
  useEffect(() => {
    const timer = setInterval(() => {
      // largeData bị giữ trong closure dù chỉ cần length
      console.log('Items count:', largeData.length);
    }, 5000);

    return () => clearInterval(timer);
  }, [largeData]);
}

// ✅ Đúng: Chỉ giữ giá trị cần thiết
function DataProcessor({ largeData }) {
  const itemCount = largeData.length;

  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Items count:', itemCount);
    }, 5000);

    return () => clearInterval(timer);
  }, [itemCount]);
}

Phần 7: Profiling và phát hiện nút thắt cổ chai

Tối ưu hiệu suất mà không đo lường thì chỉ là đoán mò. Nghiêm túc mà nói, đây là sai lầm lớn nhất mà mình thấy các dev mắc phải — tối ưu "theo cảm giác" thay vì dựa trên dữ liệu thực.

7.1 React Native DevTools - Performance Panel mới

React Native 0.83 có Performance Panel mới trong DevTools. Khá dễ dùng:

// Bật DevTools trong development
// Mở terminal và nhấn 'j' để mở DevTools

// Trong DevTools:
// 1. Mở tab "Performance"
// 2. Nhấn nút Record
// 3. Thực hiện thao tác bạn muốn profile
// 4. Nhấn Stop
// 5. Phân tích flame graph và timeline

// Bật highlight re-renders:
// DevTools > Settings > "Highlight updates when components render"

7.2 React Profiler API

Bạn có thể đo thời gian render từng component bằng <Profiler> API — cực kỳ hữu ích khi muốn biết chính xác component nào đang "ăn" thời gian:

import React, { Profiler } from 'react';

function onRenderCallback(
  id,           // Tên phần tử Profiler
  phase,        // "mount" hoặc "update"
  actualDuration, // Thời gian render thực tế (ms)
  baseDuration,   // Thời gian render nếu không có memo (ms)
  startTime,
  commitTime
) {
  // Log component render chậm (> 16ms = dưới 60 FPS)
  if (actualDuration > 16) {
    console.warn(
      `[Perf] ${id} ${phase}: ${actualDuration.toFixed(1)}ms ` +
      `(base: ${baseDuration.toFixed(1)}ms)`
    );
  }
}

// Bọc component bạn muốn theo dõi
function App() {
  return (
    
      
    
  );
}

7.3 Đo thời gian khởi động chính xác

// utils/performanceMarks.js
const marks = {};

export function markStart(label) {
  marks[label] = performance.now();
}

export function markEnd(label) {
  if (marks[label]) {
    const duration = performance.now() - marks[label];
    console.log(`[Perf] ${label}: ${duration.toFixed(1)}ms`);
    delete marks[label];
    return duration;
  }
  return null;
}

// Sử dụng trong App.js
import { markStart, markEnd } from './utils/performanceMarks';

// Đánh dấu ngay đầu file
markStart('app-startup');

function App() {
  useEffect(() => {
    const startupTime = markEnd('app-startup');

    if (__DEV__ && startupTime > 2000) {
      console.warn(
        `App startup quá chậm: ${startupTime.toFixed(0)}ms. ` +
        'Mục tiêu dưới 2000ms.'
      );
    }
  }, []);

  return ;
}

7.4 Native profiling tools

Khi cần đi sâu hơn vào tầng native, bạn có:

  • Xcode Instruments (iOS): profiling CPU, Memory, GPU rendering, Energy Impact. Đặc biệt tốt cho phát hiện memory leak native.
  • Android Studio Profiler: CPU, Memory, Network Profiler. Layout Inspector giúp kiểm tra view hierarchy.
  • Systrace: phân tích chi tiết rendering pipeline trên Android.
# Chạy profiling trên Android
npx react-native profile-hermes

# Kết quả tạo file Chrome trace
# Mở chrome://tracing trong Chrome và load file trace

Phần 8: Checklist tối ưu hiệu suất React Native 2026

Đây là checklist mà mình dùng cho mọi dự án. Bookmark lại để dùng khi cần nhé.

Khởi động ứng dụng:

  • Hermes v1 đã bật (mặc định từ SDK 55)
  • Lazy loading cho màn hình không hiển thị ngay
  • Import chọn lọc, tránh import toàn bộ thư viện lớn
  • Bundle size đã kiểm tra và tối ưu

Rendering:

  • React.memo cho component nhận props ổn định
  • useCallback cho function truyền xuống component con
  • useMemo cho tính toán phức tạp hoặc dữ liệu derived
  • Tránh tạo object/array mới trong render
  • Kiểm tra re-render dư thừa bằng DevTools

Danh sách:

  • Chọn đúng thư viện theo use case
  • getItemLayout cho item chiều cao cố định
  • keyExtractor trả về unique stable key
  • renderItem wrap trong useCallback
  • Item component wrap trong React.memo

Hình ảnh:

  • Dùng expo-image thay Image mặc định
  • Định dạng WebP cho ảnh tĩnh
  • Resize phía server theo kích thước hiển thị
  • Blurhash placeholder

Animation:

  • Dùng Reanimated 4 thay Animated API
  • Ưu tiên animate transform và opacity
  • CSS Animations cho animation đơn giản
  • Worklets cho gesture-driven animation

Bộ nhớ:

  • Cleanup subscription và timer trong useEffect
  • AbortController cho HTTP requests
  • Tránh giữ reference lớn trong closure
  • Kiểm tra memory leak bằng native profiling tools

Kết luận

Tối ưu hiệu suất React Native năm 2026 không còn là chuyện "mò mẫm" nữa. Với Hermes v1, Kiến Trúc Mới, FlashList v2, Reanimated 4, và bộ công cụ profiling hiện đại, bạn có đủ vũ khí để xây dựng app mượt mà và nhanh chóng.

Nhưng điều quan trọng nhất vẫn là: đo lường trước, tối ưu sau. Đừng tối ưu "mù" — dùng profiler để xác định nút thắt cổ chai thực sự, rồi áp dụng đúng kỹ thuật. Và luôn test trên thiết bị thật, đặc biệt Android tầm trung, vì đó là nơi vấn đề hiệu suất bộc lộ rõ nhất.

Hãy bắt đầu bằng việc mở React Native DevTools, bật highlight re-renders, và xem app của bạn đang render bao nhiêu lần không cần thiết. Kết quả có thể sẽ khiến bạn ngạc nhiên đấy.

Về Tác Giả Editorial Team

Our team of expert writers and editors.