الوحدة النمطية الأصلية هي فئة Java أو Objective-C تجعل واجهات برمجة التطبيقات الأصلية للمنصة متاحة من JavaScript في React Native. يتم تسجيل كل وحدة في Bridge وتصدر طرقًا يمكن استدعاؤها من كود JS كوظائف عادية. وفقًا لـ Meta، 2024، تظل الوحدة النمطية الأصلية الطريقة الرئيسية لدمج كود المنصة في تطبيقات React Native.
النقاط الرئيسية
الوحدة النمطية الأصلية هي عنصر معماري في React Native يسمح بتنفيذ كود بلغة المنصة (Objective-C/Swift لنظام iOS، Java/Kotlin لنظام Android) وإرجاع النتيجة إلى JavaScript. بدون الوحدة النمطية الأصلية، من المستحيل الوصول إلى إمكانيات الجهاز الأصلية — الكاميرا، GPS، مقياس التسارع، نظام الملفات أو Bluetooth.
تأتي React Native مع مجموعة من الوحدات النمطية الأصلية المدمجة: CameraRoll وAsyncStorage وGeolocation وNetInfo وغيرها. ومع ذلك، للمهام المحددة — دمج SDK لجهات خارجية، العمل مع أجهزة الاستشعار أو العمليات الخلفية — يقوم المطور بإنشاء وحدات مخصصة. وفقًا لاستطلاع State of React Native 2024، يستخدم 67% من المطورين وحدة نمطية أصلية مخصصة واحدة على الأقل في مشاريعهم.
تعتمد هندسة الوحدة النمطية الأصلية على إصدار React Native. في الهندسة الكلاسيكية (React Native 0.72 والإصدارات الأقدم) تتصل الوحدة عبر Bridge وتتواصل مع JS بشكل غير متزامن عبر تسلسل JSON. في الهندسة الجديدة (React Native 0.76+) يمكن للوحدة العمل كـ وحدة فائقة، باستخدام JSI للوصول المتزامن دون تسلسل.
يبدأ إنشاء وحدة نمطية أصلية لنظام iOS بتعريف فئة Objective-C تنفذ بروتوكول RCTBridgeModule. يقوم ماكرو RCT_EXPORT_MODULE بتسجيل الوحدة في Bridge، بينما يقوم RCT_EXPORT_METHOD بتصدير طريقة متاحة من JavaScript.
// ImageCompressor.m — الوحدة النمطية الأصلية لنظام 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
تستقبل طريقة compressImage مسار الصورة وجودة الضغط (0.0–1.0)، وتعالج البيانات على الجانب الأصلي وتعيد المسار إلى الملف المضغوط. الميزة الرئيسية هي أن الضغط يتم بواسطة كود أصلي، وهو أسرع وأكثر كفاءة في الذاكرة بشكل ملحوظ من العملية المكافئة في JavaScript.
لوحدات Swift، يتم استخدام التعليق @objc قبل الفئة والطرق لجعلها متاحة لوقت تشغيل Objective-C الذي يعمل به Bridge. يجب أن ترث الفئة من NSObject وتنفذ RCTBridgeModule.
// ImageCompressor.swift — وحدة Swift الأصلية
@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)
}
}
في Android، يتم إنشاء وحدة نمطية أصلية كفئة Java ترث من ReactContextBaseJavaModule. يقوم التعليق @ReactMethod بتصدير الطريقة إلى Bridge. يتم استخدام واجهة Promise من com.facebook.react.bridge لإرجاع النتائج.
// ImageCompressorModule.java — الوحدة النمطية الأصلية لنظام 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());
}
}
}
تقوم طريقة getName() بإرجاع اسم الوحدة الذي ستكون متاحة به من JavaScript. في المثال أعلاه، تم تسجيل الوحدة باسم ImageCompressor. يشير التعليق @ReactMethod إلى Bridge أنه يجب تصدير الطريقة. مهم: يجب أن تكون الطرق من نوع void وأن تقبل فقط الأنواع المدعومة من Bridge: String وBoolean وInteger وDouble وReadableArray وReadableMap وPromise.
بعد إنشاء فئة الوحدة، يجب تسجيلها في حزمة التطبيق. لهذا، يتم إنشاء فئة تنفذ ReactPackage وإضافتها إلى قائمة الوحدات في طريقة createNativeModules.
// ImageCompressorPackage.java — تسجيل الوحدة
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();
}
}
بعد إنشاء الوحدات لكلا المنصتين، يجب تسجيلها في React Native. لنظام Android، يتم إضافة الحزمة في MainApplication.java في طريقة getPackages(). لنظام iOS، يتم تسجيل الوحدة تلقائيًا عبر ماكرو RCT_EXPORT_MODULE، ولكن يمكن أيضًا استخدام التسجيل اليدوي في AppDelegate.mm.
// MainApplication.java — إضافة الحزمة إلى 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;
}
};
}
بعد التسجيل، تصبح الوحدة متاحة في JavaScript عبر NativeModules. تستخدم React Native تلقائيًا اسم الوحدة المحدد في getName() لنظام Android أو RCT_EXPORT_MODULE لنظام iOS.
// استخدام الوحدة النمطية الأصلية من 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('تم الضغط:', result);
return result;
} catch (error) {
console.error('فشل الضغط:', error);
throw error;
}
}
مقارنة الوحدة النمطية الأصلية الكلاسيكية مع الوحدة الفائقة تساعد في فهم أي نهج تختار لمشروع جديد. توفر كلتا الآليتين الوصول إلى الكود الأصلي، لكنهما تختلفان جوهريًا في الهندسة والأداء.
| الخاصية | الوحدة النمطية الأصلية (Bridge) | الوحدة الفائقة (JSI) |
|---|---|---|
| الاتصال | غير متزامن عبر JSON | متزامن عبر JSI |
| التسلسل | JSON في كل استدعاء | بدون نسخ بيانات |
| الكتابة | يدوي، بدون توليد | تلقائي عبر Codegen |
| التحميل | عند تهيئة التطبيق | تحميل بطيء (lazy load) |
| التوافق | جميع إصدارات React Native | React Native 0.73+ |
للمشاريع الحالية على React Native 0.72 والإصدارات الأقدم، تظل الوحدات النمطية الأصلية الكلاسيكية هي الخيار الرئيسي. للمشاريع الجديدة، يُوصى باستخدام الوحدة الفائقة، خاصة إذا كان الأداء العالي مطلوبًا في الاستدعاءات المتكررة للطرق الأصلية. مع التحديث التدريجي لـ React Native، يتحرك المجتمع نحو الانتقال الكامل إلى الهندسة الجديدة.
الأسئلة الشائعة
نعم، يدعم Bridge الـ callbacks. بدلاً من Promise، يمكن استخدام دوال callback RCTResponseSenderBlock في iOS وCallback في Android. ومع ذلك، يعتبر Promise المعيار الحديث ويُوصى به للوحدات الجديدة.
يتم تصحيح أخطاء الوحدة النمطية الأصلية ككود أصلي عادي — ضع نقاط توقف في Xcode أو Android Studio. لنظام iOS، استخدم مخطط البناء مع React Native؛ لنظام Android، استخدم تهيئة Debug. نقطة الدخول هي الطرق التي يتم استدعاؤها من JS.
الوحدة النمطية الأصلية عبر Bridge لا تدعم البيانات الثنائية (NSData/byte[]) أو الكائنات المخصصة أو الدوال. للصور، استخدم مسار الملف أو سلسلة base64. الوحدة الفائقة عبر JSI تزيل بعض هذه القيود.
عادة لا — واجهة برمجة الوحدة النمطية الأصلية مستقرة ومتوافقة مع الإصدارات السابقة. عند الانتقال إلى الهندسة الجديدة (الوحدة الفائقة)، يتم تكييف الوحدة من خلال الديكورات، لكن الكود الموجود يستمر في العمل.
استخدم RCTEventEmitter في iOS أو DeviceEventEmitter في Android. ترسل الوحدة حدثًا، ويشترك جانب JS عبر NativeEventEmitter من react-native. هذا مفيد لبيانات التدفق وأحداث الاستشعار.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.