Падчыненне модуля Turbo ўнутрь аплікацыі з нагадкай пра канец-канец за дапамойкою кодгенеравання React Native
З’явіце типаваны спэк, запусціце кодгенераванне і рэалізуйце Turbo Module на iOS і Android з сінхроннымі, Promise, callback і методамі event-emitter.
Новая архітектура React Native заменяе асінхронны мост на Turbo Modules, які JavaScript запоўняе за дапамогою JSI з типаванымі, генераванымі прыўязкамі. У результате зменшуецца навантажэння ад вызоў і контракт пераказваецца за дапамогою codegen, а не на падставе традыцый. У цім кераванні ствараецца адна модуль унутрь прыкладнага програміста (без пакета npm) і розглядаюцься тры стылі вызоў, якія вы будете викорыстоўваць на практыцы: сінхронная значэння, асінхронныя рызультаты через Promise або callback, і струм лэўкі.
Стварэнне типаванага спецыфікатора
Створыце папку specs у корні проекта, куды будуць зберагвацца схемы модуляў:
/your-app
/specs
Усередзіне яго дадзіце NativeShowCase.ts. Кампанія Codegen прыймае толькі файлы спэсыфікацый, назвы якіх пачынаюцца з Native. Наведзенае нижэй спэсыфікацыя прызначае по аднаму методу на кожны стыль: haveCameraFlash вяртае рэзультат сінхронна, toggleFlashLight вяртае Promise, fetchUser прыймае калебэк, а evenListenerCalled — это эмітэр падзеяў, які перавозы пары ключ/значэнне. getEnforcing выклікае абярошчанне пад час запуску, якщо натыўная частка не ўсёродзіма, што дазволяе рана выявіць памылкі у наладзе. Тыпаваныя эмітэры падзеяў у спэсыфікацыях — это новая функцыя, таму пераканайцеся, што ваша версія React Native падтрымляе CodegenTypes.EventEmitter.
import type { CodegenTypes, TurboModule } from "react-native";
import { TurboModuleRegistry } from "react-native";
export type KeyValuePair = {
key: string;
value: string;
};
export interface Spec extends TurboModule {
toggleFlashLight(): Promise<boolean>;
haveCameraFlash(): boolean;
fetchUser(
id: string,
callback: (user: { id: string; name: string }) => void
): void;
readonly evenListenerCalled: CodegenTypes.EventEmitter<KeyValuePair>;
}
export default TurboModuleRegistry.getEnforcing<Spec>("NativeShowCase");
Этот файл є едынай адпаведную інформацыяй; наладкі для обох платформ генеруюцца з яго.
Настройка і запуск кампаніи Codegen
Дадзіце блок codegenConfig у файл package.json. Пашка jsSrcsDir павінна супадаць з пашкай вашых спефікацый, а javaPackageName вялічынюе месца, дзе будуць генеравацься класы для Android:
"codegenConfig": {
"name": "NativeShowCase",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.nativeshowcase"
}
}
Потым генеруйце рэзультаты. Для Android запускайце задачу Gradle:
cd android && ./gradlew generateCodegenArtifactsFromSchema
Для iOS процес кодгену выканаецца ў рамках інсталляціі pods:
cd ios && pod install
Тепер у вас ўжо є створаныя інтэрфейсы, заголовкі та дапаможны код для обох платформ.
Імплементацыя модуля на iOS
Ачніце файл .xcworkspace у Xcode, створыце групу NativeShowCase і дадзіце клас на мове Objective-C пад назвай RCTNativeShowCase. Пераявіце файл .m у .mm: для Turbo Modules патрэбны Objective-C++ каб было можна саўместна працаваць з шаром C++ JSI.
Загалоўка імпортуе ствараны модуль, успадковвае яго базовую класу і адмаўляе ствараны протакол:
#import <Foundation/Foundation.h>
#import <NativeShowCase/NativeShowCase.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTNativeShowCase : NativeShowCaseSpecBase<NativeShowCaseSpec>
@end
NS_ASSUME_NONNULL_END
Адмаўленне вяртае назву модуля, передае React Native об’ект JSI з getTurboModule і адмаўляе кожны з методаў, задакладзеных у спэкіфікацыі. toggleFlashLight рашае задачу Promise, fetchUser вызывае калбэк, а haveCameraFlash вяртае рэзультат адразу, але запускае таймер, які выдае дзесяць змянэнняў через ствараны метод emitEvenListenerCalled.
#import "RCTNativeShowCase.h"
@interface RCTNativeShowCase()
@property (nonatomic, strong) NSTimer *myTimer;
@property (nonatomic, assign) int count;
@end
@implementation RCTNativeShowCase
+ (NSString *)moduleName {
return @"NativeShowCase";
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeShowCaseSpecJSI>(params);
}
- (void)toggleFlashLight:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
resolve(@(false));
}
- (nonnull NSNumber *)haveCameraFlash {
[self startTimer];
return [NSNumber numberWithBool:true];
}
- (void)fetchUser:(nonnull NSString *)userId callback:(nonnull RCTResponseSenderBlock)callback {
callback(@[@{@"key": userId, @"value": @"John"}]);
}
-(void)startTimer {
if(self.myTimer) {
[self stopTimer];
}
self.count = 0;
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateCount)
userInfo:nil
repeats:YES];
}
- (void)updateCount {
self.count += 1;
[self emitEvenListenerCalled:@{@"key": @"count", @"value": @(_count)}];
if (self.count == 10) {
[self stopTimer];
}
}
- (void)stopTimer {
[self.myTimer invalidate];
self.myTimer = nil;
}
@end
Заўважыце, што цей iOS-метод fetchUser адправляе поля key/value, тады как спэкіфікацыя выказваеся на id і name; паспрабуйце прыравняць іх у рэальным кодзе, адтакулькі генераванне коду не пераказвае формат падання калбэка пад час выконання.
Адмаўленне модуля на Android
Створыце пакет com.nativeshowcase, які згаданы ў codegenConfig. Клас модуля расшырвае створаны NativeShowCaseSpec і перадае кожны метод: ён зміняе стан флага і рашылае Promise, запускае CountDownTimer, які звяртае залишыцца секунды пасля кожнага таймінгу, і стварае карточку для калебака.
package com.nativeshowcase
import android.os.CountDownTimer
import android.widget.Toast
import com.facebook.fbreact.specs.NativeShowCaseSpec
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
class NativeShowCaseModule(reactContext: ReactApplicationContext) :
NativeShowCaseSpec(reactContext) {
var timer: CountDownTimer? = null
var lightOn = false
override fun toggleFlashLight(promise: Promise?) {
Toast.makeText(reactApplicationContext, "Let's turn on flash light", Toast.LENGTH_LONG)
.show()
lightOn = !lightOn
promise?.resolve(lightOn)
}
override fun haveCameraFlash(): Boolean {
if (timer != null) {
timer?.cancel()
timer = null
}
timer = object : CountDownTimer(10000, 1000) {
override fun onTick(millisUntilFinished: Long) {
val eventData = Arguments.createMap().apply {
putString("key", "count")
putInt("value", (millisUntilFinished / 1000).toInt())
}
emitEvenListenerCalled(eventData)
}
override fun onFinish() {
timer?.cancel()
timer = null
}
}
timer?.start()
return true
}
override fun fetchUser(id: String?, callback: Callback?) {
val eventData = Arguments.createMap().apply {
putString("id", id)
putString("name", "John")
}
callback.let { it?.invoke(eventData) }
}
companion object {
const val NAME = "NativeShowCase"
}
override fun getName() = NAME
}
Клас пакета, створаны на базе BaseReactPackage, паведамляе React Native, як інстанціювацыяваць модуль. Флаг isTurboModule = true у яго ReactModuleInfo ўпрабоўвае яго через новую систему.
package com.nativeshowcase
import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
class NativeShowCasePackage : BaseReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
if (name == NativeShowCaseModule.NAME) return NativeShowCaseModule(reactContext)
return null
}
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
mapOf(
NativeShowCaseModule.NAME to ReactModuleInfo(
name = NativeShowCaseModule.NAME,
className = NativeShowCaseModule.NAME,
canOverrideExistingModule = false,
needsEagerInit = true,
isCxxModule = false,
isTurboModule = true
)
)
}
}
На завершанне зарэгіструйце пакет у MainApplication.kt, дадаўшы яго да спісу автаматычнаясоедзінаных пакетаў:
class MainApplication : Application(), ReactApplication {
override val reactHost: ReactHost by lazy {
getDefaultReactHost(
context = applicationContext,
packageList =
PackageList(this).packages.apply {
add(NativeShowCasePackage())
},
)
}
override fun onCreate() {
super.onCreate()
loadReactNative(this)
}
}
Выкліканне модуля з React
Імпортуйце стандартны вывод спэкі і выкалывайце яго як будзь-яны об’ект. Эфект падпісваецца на эмітэра запуска і вяртае функцыю для чысткі, якая адключае слухальнік праз unmount; адна кнопка выклікае сінхронны метод, іншая — методы Promise і callback.
import { StatusBar, StyleSheet, useColorScheme, View, Button } from 'react-native';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
import NativeShowCase from './specs/NativeShowCase';
import { useEffect } from 'react';
function App() {
const isDarkMode = useColorScheme() === 'dark';
return (
<SafeAreaProvider>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<AppContent />
</SafeAreaProvider>
);
}
function AppContent() {
useEffect(() => {
const listener = NativeShowCase.evenListenerCalled(pair => {
console.log('Event received:', pair);
});
return () => listener.remove();
}, []);
return (
<View style={styles.container}>
<Button
title="Have Camera Flash?"
onPress={() => {
console.log('Have Camera Flash:', NativeShowCase.haveCameraFlash());
}}
/>
<Button
title="Toggle Flashlight"
onPress={() => {
NativeShowCase.toggleFlashLight()
.then(isOn => console.log('Flashlight:', isOn))
.catch(err => console.error(err));
NativeShowCase.fetchUser('123', user => {
console.log('Fetched user:', user);
});
}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
});
export default App;
Вжывайце сінхронныя методы адзіно: яны блакуець нитку JavaScript пакуль не будзе вярнута рэсурсная версія, таму застаўляйце іх для простых пошукоў. Чыба не альтернатыўны падход да прыяўлення, паглядзіце, як Nitro Modules паўступаюць з Turbo Modules.
Ключовыя выводы
- Спэк TypeScript — гэта контракт; codegen выводзіць з яго рэсурсныя інтарфейсы для обох платформ.
- Называйце файлы спэкі з прыметнікам
Nativeі падтрымайце сінхроннасцьcodegenConfigз папкамі і пакетамі.
.mm; у пакетах для Android неабяжна пазначыць модулі з isTurboModule.Спадні матэрыялы
- Рулінг пуш-паведамленняў через глыбокія лінкі ў React Native — налаштаваеце універсальныя лінкі і лінкі дапытку, падключаеце Pusher Beams да дапытку React Native і супарабатоўваеце веб-URL-ы з натыўнымі экранамі, каб клік па паведамленні ачынаў правильны элемент.
- Планаванне апгрэйда Expo SDK 58: iOS 27, React Native 0.88 і новыя адпрамкі — Практычны абзор бета-версіі Expo SDK 58: якія змены для iOS 27 і React Native 0.88, якія функцыі ў прымэтнай версіі, і як безпечна перацягнуць апдэйт.
- Аднашчэнне натыўных папак як выходу пад час будовы з адпрамкамі Expo Prebuild і CNG — Як Continuous Native Generation дазволяе аплікацыям Expo выкарыстоўваць спецыяльныя натыўныя модулі, плагіні налаштавання і секрэты EAS без неабходнасці зберагаць чы рэкамендаваць папкі ios і android.
- Уведамленні ў React Native: дазволы, каналы і цикл жыцця FCM — Дазведзіце сабе, як дазволы, каналы Android, токены FCM і працэўнікі для станоў фарграунда, бэкграунда і завершэння працы адносуюцца ўмаўленае ў систему уведамленняў React Native з Notifee.
- Самастаўленыя апдэйты праз канец-канец для чыстага React Native з hot-updater і Supabase — Наладзіце апдэйты JavaScript праз канец-канец у дапытку чыстага React Native з hot-updater і Supabase: ініцыялізацыя, функцыя тыхога апдэйту, каналы, скрыпты развяртання і адкат.