1. Đặt vấn đề: Cuộc chiến 60 FPS và Hạn chế của Kiến trúc Native Bridge Cũ

Trong phát triển ứng dụng di động đa nền tảng, việc duy trì tốc độ khung hình ổn định ở mức 60 FPS (hoặc 120 FPS trên các màn hình ProMotion hiện đại) là tiêu chuẩn vàng định nghĩa trải nghiệm người dùng mượt mà. Tuy nhiên, đối với kiến trúc truyền thống của React Native (Old Architecture), các nhà phát triển thường xuyên gặp phải tình trạng đơ lag (stuttering), mất khung hình (frame drop) khi thực hiện các tác vụ phức tạp như animation kéo vuốt danh sách, đồng bộ hóa cử chỉ tay hoặc xử lý dữ liệu stream thời gian thực.

Nguyên nhân cốt lõi nằm ở cách thức giao tiếp thông qua Asynchronous JSON Bridge. Kiến trúc cũ chia ứng dụng thành ba luồng chính:

  • JS Thread: Nơi thực thi mã nguồn JavaScript/TypeScript và logic của ứng dụng.
  • UI Thread (Main Thread): Luồng chịu trách nhiệm render giao diện native và hứng các sự kiện tương tác từ người dùng.
  • Shadow Thread: Nơi tính toán layout bằng engine Yoga trước khi chuyển hóa thành các thuộc tính native UI.

Mỗi khi luồng JS muốn cập nhật một thuộc tính UI (ví dụ: vị trí của một thẻ khi kéo thả), dữ liệu phải được tuần tự hóa (serialize) thành chuỗi JSON, xếp vào hàng đợi (queue), gửi qua Bridge, sau đó luồng Native giải tuần tự hóa (deserialize) và mới tiến hành cập nhật giao diện. Khi tần suất gửi dữ liệu quá cao (như sự kiện onScroll bắn ra 60-120 lần/giây), Bridge rơi vào trạng thái quá tải (bottleneck), dẫn đến việc UI Thread phải chờ đợi JS Thread và hiện tượng drop frame xảy ra.

2. Bản chất Kiến trúc Mới: JavaScript Interface (JSI) và TurboModules

Để khắc phục hoàn toàn điểm nghẽn này, nhóm phát triển React Native đã tái thiết kế toàn bộ hệ thống với kiến trúc New Architecture, trong đó trọng tâm là JavaScript Interface (JSI).

2.1 Cơ chế hoạt động của JSI (JavaScript Interface)

JSI không còn sử dụng cơ chế truyền tin nhắn bất đồng bộ qua JSON Bridge. Thay vào đó, JSI là một lớp thư viện C++ lightweight cho phép engine JavaScript (như Hermes hoặc V8) trực tiếp giữ tham chiếu đến các đối tượng C++ Native (Host Objects) và ngược lại.

Điều này đồng nghĩa với việc mã JavaScript có thể gọi trực tiếp các phương thức của Native Module theo cơ chế đồng bộ (synchronous invocation) tương tự như việc gọi một hàm JavaScript thông thường, với chi phí thềm (overhead) gần như bằng 0.

// Mô phỏng tư tưởng gọi phương thức Native qua JSI không tốn chi phí Serialize JSON
// JS Engine giữ trực tiếp pointer tới C++ Host Object
const nativeCalculator = global.NativeCalculator; 

// Gọi trực tiếp synchronous C++ function từ JS Thread
const result = nativeCalculator.addDirectly(15, 30); 
console.log(result); // Output: 45 - Trả về ngay lập tức không thông qua Bridge Queue

2.2 So sánh hiệu năng giữa Bridge và JSI

  • Không tuần tự hóa JSON: Loại bỏ hoàn toàn CPU cycle lãng phí cho việc JSON.stringify()JSON.parse().
  • Truy cập bộ nhớ dùng chung (Shared Memory): C++ Host Objects cho phép JS và Native truy cập chung một vùng nhớ, hỗ trợ truyền các mảng dữ liệu lớn (ArrayBuffer) cực kỳ nhanh chóng.
  • Khởi tạo lười (Lazy Loading với TurboModules): Native Modules không còn phải tải đồng loạt khi ứng dụng khởi động. Module chỉ được nạp vào bộ nhớ khi JS code thực sự gọi đến.

3. Kỹ thuật Tối ưu hóa Animation dựa trên Reanimated 3 và UI Thread Worklets

Dù JSI cho phép gọi hàm đồng bộ, nếu toàn bộ tính toán animation phức tạp vẫn đè nặng lên JS Thread, ứng dụng vẫn có thể bị đơ nếu JS Thread đang bận thực thi logic nghiệp vụ khác (như parse dữ liệu API lớn). Giải pháp triệt để là đưa mã tính toán animation sang thực thi trực tiếp trên UI Thread thông qua khái niệm JS Worklets.

3.1 Khái niệm UI Thread Worklet

Worklet là một đoạn mã JS ngắn được biên dịch đặc biệt để có thể chạy tách biệt trên một JS Context phụ nằm ngay tại UI Thread. Nhờ có JSI, Worklet có thể thao tác trực tiếp với thuộc tính của Native View mà không cần quay lại JS Thread.

3.2 Thực chiến: Xây dựng Custom Swipeable Card với Gesture Handler và Reanimated 3

Dưới đây là ví dụ triển khai thực tế một thành phần thẻ có thể vuốt sang hai bên với phản hồi động lực học (spring physics) mượt mà 60 FPS, áp dụng TypeScript và Reanimated 3.

import React from 'react';
import { StyleSheet, View, Text, Dimensions } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  interpolate,
  Extrapolation,
  runOnJS,
} from 'react-native-reanimated';

const { width: SCREEN_WIDTH } = Dimensions.get('window');
const SWIPE_THRESHOLD = SCREEN_WIDTH * 0.3;

interface SwipeableCardProps {
  onDismiss?: () => void;
  children: React.ReactNode;
}

export const SwipeableCard: React.FC<SwipeableCardProps> = ({ onDismiss, children }) => {
  // SharedValue lưu trữ trạng thái vị trí trên UI Thread
  const translateX = useSharedValue(0);

  // Định nghĩa callback chuyển luồng về JS Thread khi hoàn tất thao tác
  const handleDismiss = () => {
    if (onDismiss) {
      onDismiss();
    }
  };

  // Khai báo Gesture xử lý kéo vuốt
  const panGesture = Gesture.Pan()
    .onChange((event) => {
      // Hàm này chạy dạng Worklet trên UI Thread
      translateX.value += event.changeX;
    })
    .onEnd(() => {
      if (Math.abs(translateX.value) > SWIPE_THRESHOLD) {
        const targetX = translateX.value > 0 ? SCREEN_WIDTH : -SCREEN_WIDTH;
        translateX.value = withSpring(targetX, { velocity: 50 }, (isFinished) => {
          if (isFinished && onDismiss) {
            runOnJS(handleDismiss)();
          }
        });
      } else {
        // Trả về vị trí ban đầu với hiệu ứng Spring
        translateX.value = withSpring(0, { damping: 15, stiffness: 90 });
      }
    });

  // Style biến đổi được tính toán trực tiếp trên UI Thread
  const rCardStyle = useAnimatedStyle(() => {
    const rotate = interpolate(
      translateX.value,
      [-SCREEN_WIDTH, 0, SCREEN_WIDTH],
      [-15, 0, 15],
      Extrapolation.CLAMP
    );

    const opacity = interpolate(
      translateX.value,
      [-SCREEN_WIDTH * 0.8, 0, SCREEN_WIDTH * 0.8],
      [0, 1, 0],
      Extrapolation.CLAMP
    );

    return {
      transform: [
        { translateX: translateX.value },
        { rotate: `${rotate}deg` },
      ],
      opacity,
    };
  });

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={[styles.card, rCardStyle]}>
        {children}
      </:Animated.View>
    </GestureDetector>
  );
};

const styles = StyleSheet.create({
  card: {
    width: SCREEN_WIDTH - 40,
    height: 200,
    backgroundColor: '#ffffff',
    borderRadius: 16,
    padding: 20,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 8 },
    shadowOpacity: 0.1,
    shadowRadius: 12,
    elevation: 5,
    alignSelf: 'center',
    justifyContent: 'center',
  },
});

4. Quản lý Bộ nhớ và Tránh Memory Leak khi Viết C++ TurboModule

Khi tiến sâu vào việc xây dựng các TurboModule tự định nghĩa bằng C++ kết hợp JSI, vấn đề quản lý bộ nhớ trở nên tối quan trọng. JavaScript có cơ chế dọn rác tự động (Garbage Collection), trong khi C++ yêu cầu quản lý thủ công (hoặc qua Smart Pointers).

4.1 Sử dụng jsi::HostObject an toàn với Smart Pointers

Khi tạo một C++ Host Object để truyền dữ liệu hoặc phương thức cho JavaScript, bạn phải đảm bảo không tạo ra hiện tượng vòng lặp giữ tham chiếu (Circular Reference) dẫn đến rò rỉ bộ nhớ (Memory Leak).

// Ví dụ khai báo một C++ JSI HostObject tối ưu bộ nhớ
#pragma once
#include <jsi/jsi.h>
#include <memory>

namespace facebook {
namespace react {

class JSI_EXPORT HighPerformanceBuffer : public jsi::HostObject {
public:
  HighPerformanceBuffer(size_t size) : size_(size) {
    data_ = std::make_unique<uint8_t[]>(size);
  }

  ~HighPerformanceBuffer() override {
    // Thu hồi bộ nhớ C++ an toàn khi JS Garbage Collector giải phóng HostObject
    data_.reset();
  }

  jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override {
    auto propName = name.utf8(runtime);
    if (propName == "getSize") {
      return jsi::Function::createFromHostFunction(
        runtime,
        name,
        0,
        [this](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) -> jsi::Value {
          return jsi::Value(static_cast<double>(this->size_));
        }
      );
    }
    return jsi::Value::undefined();
  }

private:
  size_t size_;
  std::unique_ptr<uint8_t[]> data_;
};

} // namespace react
} // namespace facebook

4.2 Quy tắc vàng để tránh leak bộ nhớ trong kiến trúc JSI

  1. Không lưu trữ jsi::Value dài hạn trong C++ Scope: Các biến jsi::Value được ràng buộc với lifecycle của jsi::Runtime. Nếu lưu trữ chúng trong các biến toàn cục C++ mà không dọn dẹp, GC của JavaScript sẽ không thể giải phóng vùng nhớ đó.
  2. Sử dụng jsi::WeakObject khi cần reference đến JS Object: Trường hợp C++ cần giữ tham chiếu đến một Object của JS nhưng không muốn ngăn cản GC thu gom nó, hãy bọc đối tượng đó trong jsi::WeakObject.
  3. Tránh gọi back ngược về JS Thread quá dày đặc: Việc dùng runOnJS liên tục từ các callback C++ ở tần suất cao sẽ vô hiệu hóa ưu thế về mặt hiệu năng của JSI.

5. Chiến lược Profiling và Debug Hiệu năng Ứng dụng

Để đánh giá chính xác ứng dụng có đạt chuẩn 60 FPS hay không, lập trình viên không nên dựa vào cảm giác mắt thường mà cần sử dụng các công cụ đo đạc chuyên dụng:

  • React Native Perf Monitor: Bật Menu Dev (Shake gesture) và chọn Show Perf Monitor để theo dõi song song UI FPS và JS FPS. Nếu UI FPS sụt giảm, vấn đề nằm ở cấu trúc Native Layout hoặc hiệu ứng chuyển trang. Nếu JS FPS sụt giảm, JS Thread đang bị tắc nghẽn bởi logic tính toán nặng.
  • Flipper / React DevTools Profiler: Xác định các component bị re-render vô đòn (unnecessary re-renders) và tối ưu hóa bằng React.memo, useMemo, useCallback.
  • Xcode Instruments (dành cho iOS) / Android Profiler: Đo lường chi tiết mức độ tiêu tốn CPU, dung lượng bộ nhớ RAM (Heap Allocation) và phát hiện rò rỉ bộ nhớ ở tầng Native C++/Obj-C/Kotlin.

6. Kết luận

Tối ưu hóa hiệu năng ứng dụng React Native lên mức hoàn hảo 60 FPS đòi hỏi nhà phát triển phải thấu hiểu sâu sắc bản chất của hệ thống: từ mô hình Threading Model, cơ chế hoạt động của JSI/TurboModules cho đến việc làm chủ các công cụ hỗ trợ như Reanimated 3 và C++ Host Objects. Việc chuyển dịch tư duy từ lập trình UI thuần túy sang quản lý bộ nhớ và điều phối luồng thực thi chính là bước ngoặt phân định giữa một Developer tầm trung và một Senior Mobile Architect.

Để làm chủ toàn bộ các kỹ thuật chuyên sâu này, từ tư duy lập trình TypeScript chuẩn mực, quản lý state phức tạp cho đến việc xây dựng ứng dụng thực tế chạy mượt mà trên cả iOS và Android, bạn có thể Tham khảo khóa học "Lập trình App Mobile với React Native + TypeScript" tại đây.