Native Module là một lớp Java hoặc Objective-C giúp các API gốc của nền tảng có thể truy cập được từ JavaScript trong React Native. Mỗi mô-đun được đăng ký trong Bridge và xuất các phương thức có thể được gọi từ mã JS như các hàm thông thường. Theo Meta, 2024, Native Module vẫn là cách chính để tích hợp mã nền tảng vào các ứng dụng React Native.
Những điểm chính
Native Module là một thành phần kiến trúc của React Native cho phép thực thi mã bằng ngôn ngữ nền tảng (Objective-C/Swift cho iOS, Java/Kotlin cho Android) và trả kết quả về JavaScript. Nếu không có Native Module, không thể truy cập các khả năng gốc của thiết bị — camera, GPS, gia tốc kế, hệ thống tệp hoặc Bluetooth.
React Native đi kèm với một tập hợp các Native Module tích hợp sẵn: CameraRoll, AsyncStorage, Geolocation, NetInfo và các mô-đun khác. Tuy nhiên, đối với các tác vụ cụ thể — tích hợp SDK bên thứ ba, làm việc với cảm biến phần cứng hoặc quy trình nền — nhà phát triển tạo các mô-đun tùy chỉnh. Theo khảo sát State of React Native 2024, 67% nhà phát triển sử dụng ít nhất một Native Module tùy chỉnh trong các dự án của họ.
Kiến trúc Native Module phụ thuộc vào phiên bản React Native. Trong kiến trúc cổ điển (React Native 0.72 trở về trước), mô-đun kết nối qua Bridge và giao tiếp với JS không đồng bộ thông qua tuần tự hóa JSON. Trong kiến trúc mới (React Native 0.76+), mô-đun có thể hoạt động như Turbo Module, sử dụng JSI để truy cập đồng bộ mà không cần tuần tự hóa.
Việc tạo Native Module cho iOS bắt đầu bằng việc khai báo một lớp Objective-C triển khai giao thức RCTBridgeModule. Macro RCT_EXPORT_MODULE đăng ký mô-đun trong Bridge, và RCT_EXPORT_METHOD xuất một phương thức có sẵn từ JavaScript.
// ImageCompressor.m — Native Module cho iOS
@interface ImageCompressor () RCT_EXPORT_MODULE()
@end
@implementation ImageCompressor
RCT_EXPORT_METHOD(compressImage:(NSString *)imagePath
quality:(NSNumber *)quality
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
NSData *compressedData = [UIImageJPEGRepresentation(image, quality.floatValue)];
NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"compressed.jpg"];
[compressedData writeToFile:outputPath atomically:YES];
resolve(outputPath);
}
@end
Phương thức compressImage nhận đường dẫn hình ảnh và chất lượng nén (0.0–1.0), xử lý dữ liệu ở phía gốc và trả về đường dẫn đến tệp đã nén. Ưu điểm chính là việc nén được thực hiện bằng mã gốc, nhanh hơn và hiệu quả bộ nhớ hơn đáng kể so với thao tác tương đương trong JavaScript.
Đối với các mô-đun Swift, chú thích @objc được sử dụng trước lớp và các phương thức để chúng có sẵn cho môi trường runtime Objective-C mà Bridge làm việc. Lớp phải kế thừa NSObject và triển khai RCTBridgeModule.
// ImageCompressor.swift — Mô-đun Swift Gốc
@objc(ImageCompressor)
class ImageCompressor: NSObject {
@objc
func compressImage(
_ imagePath: String,
quality: Float,
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock
) {
guard let image = UIImage(contentsOfFile: imagePath) else {
rejecter("FILE_ERROR", "Cannot load image", nil)
return
}
guard let data = image.jpegData(compressionQuality: CGFloat(quality)) else {
rejecter("COMPRESS_ERROR", "Compression failed", nil)
return
}
let outputPath = NSTemporaryDirectory() + "compressed.jpg"
try? data.write(to: URL(fileURLWithPath: outputPath))
resolver(outputPath)
}
}
Trên Android, Native Module được tạo dưới dạng lớp Java mở rộng ReactContextBaseJavaModule. Chú thích @ReactMethod xuất phương thức vào Bridge. Giao diện Promise từ com.facebook.react.bridge được sử dụng để trả về kết quả.
// ImageCompressorModule.java — Native Module cho Android
public class ImageCompressorModule
extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "ImageCompressor";
}
@ReactMethod
public void compressImage(
String imagePath,
Float quality,
Promise promise) {
try {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
File outputFile = new File(
ReactNative.getApplicationContext()
.getCacheDir(), "compressed.jpg");
FileOutputStream fos = new FileOutputStream(outputFile);
bitmap.compress(
Bitmap.CompressFormat.JPEG,
(int)(quality * 100), fos);
fos.close();
promise.resolve(outputFile.getAbsolutePath());
} catch (Exception e) {
promise.reject("COMPRESS_ERROR", e.getMessage());
}
}
}
Phương thức getName() trả về tên mô-đun mà nó sẽ có thể truy cập được từ JavaScript. Trong ví dụ trên, mô-đun được đăng ký là ImageCompressor. Chú thích @ReactMethod cho Bridge biết rằng phương thức cần được xuất. Quan trọng: các phương thức phải là void và chỉ chấp nhận các kiểu được Bridge hỗ trợ: String, Boolean, Integer, Double, ReadableArray, ReadableMap, Promise.
Sau khi tạo lớp mô-đun, nó phải được đăng ký trong gói ứng dụng. Để làm điều này, một lớp triển khai ReactPackage được tạo và thêm vào danh sách mô-đun trong phương thức createNativeModules.
// ImageCompressorPackage.java — đăng ký mô-đun
public class ImageCompressorPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.asList(
new ImageCompressorModule(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Sau khi tạo các mô-đun cho cả hai nền tảng, chúng cần được đăng ký trong React Native. Đối với Android, gói được thêm vào MainApplication.java trong phương thức getPackages(). Đối với iOS, mô-đun được đăng ký tự động qua macro RCT_EXPORT_MODULE, nhưng cũng có thể sử dụng đăng ký thủ công trong AppDelegate.mm.
// MainApplication.java — thêm gói vào React Native
import com.yourapp.nativemodules.ImageCompressorPackage;
public class MainApplication extends Application
implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages =
new PackageList(this).getPackages();
packages.add(new ImageCompressorPackage());
return packages;
}
};
}
Sau khi đăng ký, mô-đun có sẵn trong JavaScript thông qua NativeModules. React Native tự động sử dụng tên mô-đun được chỉ định trong getName() cho Android hoặc RCT_EXPORT_MODULE cho iOS.
// Sử dụng Native Module từ JavaScript
import { NativeModules } from 'react-native';
import { Platform } from 'react-native';
const ImageCompressor = NativeModules.ImageCompressor;
async function compressPhoto(uri: string) {
try {
const result = await ImageCompressor.compressImage(
uri.replace('file://', ''), 0.8
);
console.log('Đã nén:', result);
return result;
} catch (error) {
console.error('Nén thất bại:', error);
throw error;
}
}
So sánh Native Module cổ điển với Turbo Module giúp hiểu nên chọn cách tiếp cận nào cho dự án mới. Cả hai cơ chế đều cung cấp quyền truy cập vào mã gốc, nhưng khác nhau cơ bản về kiến trúc và hiệu suất.
| Đặc điểm | Native Module (Bridge) | Turbo Module (JSI) |
|---|---|---|
| Giao tiếp | Bất đồng bộ qua JSON | Đồng bộ qua JSI |
| Tuần tự hóa | JSON mỗi lần gọi | Không sao chép dữ liệu |
| Phân loại | Thủ công, không tạo tự động | Tự động qua Codegen |
| Tải | Khi khởi tạo ứng dụng | Tải lười (lazy load) |
| Tương thích | Tất cả phiên bản React Native | React Native 0.73+ |
Đối với các dự án hiện có trên React Native 0.72 trở về trước, Native Module cổ điển vẫn là lựa chọn chính. Đối với các dự án mới, Turbo Module được khuyến nghị, đặc biệt nếu yêu cầu hiệu suất cao khi gọi các phương thức gốc thường xuyên. Với việc cập nhật dần dần React Native, cộng đồng đang hướng tới quá trình chuyển đổi hoàn toàn sang kiến trúc mới.
Câu hỏi thường gặp
Có, Bridge hỗ trợ callback. Thay vì Promise, có thể sử dụng hàm callback RCTResponseSenderBlock trong iOS và Callback trong Android. Tuy nhiên, Promise được coi là tiêu chuẩn hiện đại và được khuyến nghị cho các mô-đun mới.
Native Module được gỡ lỗi như mã gốc thông thường — đặt điểm dừng trong Xcode hoặc Android Studio. Đối với iOS, sử dụng lược đồ build với React Native; đối với Android, sử dụng cấu hình Debug. Điểm vào là các phương thức được gọi từ JS.
Native Module qua Bridge không hỗ trợ dữ liệu nhị phân (NSData/byte[]), đối tượng tùy chỉnh và hàm. Đối với hình ảnh, hãy sử dụng đường dẫn tệp hoặc chuỗi base64. Turbo Module qua JSI loại bỏ một số hạn chế này.
Thường là không — API Native Module ổn định và tương thích ngược. Khi chuyển đổi sang kiến trúc mới (Turbo Module), mô-đun được điều chỉnh thông qua decorator, nhưng mã hiện có vẫn tiếp tục hoạt động.
Sử dụng RCTEventEmitter trong iOS hoặc DeviceEventEmitter trong Android. Mô-đun gửi một sự kiện và phía JS đăng ký qua NativeEventEmitter từ react-native. Điều này hữu ích cho dữ liệu phát trực tuyến và sự kiện cảm biến.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm