# 22|自定义组件:如何满足业务的个性化需求? 你好,我是众文,这一讲还是由我和惠姝来讲解。 上一讲,我们讲了如何构建混合应用。当环境配置、载体页、调试打包都 OK 后,我们就要开始复杂业务的开发了。在实际开发中,除了负责 React Native 框架本身的维护迭代外,另一个重要的工作就是配合前端业务,开发对应的 Native 组件。 那么什么时候用这些自定义的 Native 组件呢? 比如,有时候 App 需要访问平台 API,但 React Native 可能还没有相应的模块包装;或者你需要复用公司内的一些用 Java/OC 写的通用组件,而不是用 JavaScript 重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库,或者各种高级扩展等。 当然,你可以通过官方文档([Android](https://reactnative.cn/docs/native-modules-android)/[iOS](https://reactnative.cn/docs/native-modules-ios)),快速访问你的原生模块。但官方文档提供的主要是简单的 Demo 和步骤,在实际开发中,你可能还需要自定义组件的方方面面,包括新架构定义组件的全流程,以及实际业务中的踩坑指南等。 今天这一讲,我们会先带你补齐组件的相关基础知识,包括组件的生命周期、组件传输数据类型,并以新架构的TurboModule 和 Fabric 为案例,带你了解自定义组件的方方面面。你也能借此对React Native新架构建立起初步认识。接下来让我们先了解下期待已久的 React Native 新架构。 ## 新架构介绍 我们现在先通过下面这张图简单对比下新老架构: ![图片](https://static001.geekbang.org/resource/image/3e/31/3eb92714433185cd0a095yy2e1a36331.jpg?wh=1920x932) 新架构变更点主要在下面这几个方面: ![图片](https://static001.geekbang.org/resource/image/4d/8d/4df0702732be0a4922444d575980828d.png?wh=1894x1216) 前面也说了,我们一讲将以 TurboModule 和 Fabric 为案例对自定义组件进行讲解。所以你现在需要对新架构有一个初步的认识,特别是要注意,TurboModule 和 Fabric 对比旧版的 Native Module 和 UIManager 有哪些差异和优势。 如果你还想了解新架构的更多信息,你可以参考[官方文档](https://reactnative.dev/docs/new-architecture-intro)。这里包括了新架构介绍、如何在 Android、iOS 上开启新架构、如何在 Android、iOS 上开启使用 TurboModule 和 Fabric等。你也可以根据官方文档试着编译运行新架构。 而且,[react-native-screens](https://github.com/software-mansion/react-native-screens)、[react-native-gesture-handler](https://github.com/software-mansion/react-native-gesture-handler) 等知名 React Native 库的新版都已适配了新架构,感兴趣的话,你可以去了解下。 好的,现在我们进入这一讲的正题,先来了解一下组件的生命周期。 ## 组件的生命周期 组件的生命周期,指的是在组件创建、更新、销毁的过程中伴随的各种各样的函数执行。这些在组件特定的时期被触发的函数,统称为组件的生命周期。 让组件拥有生命周期,我们就可以更好地管理组件的状态、内存,跟随载体页的生命周期做相应的处理。 ### Android 在Android端,一个 Module 组件的生命周期包括: ```plain 构造 -> 初始化 -> onHostResume() -> onHostPause() -> onHostDestroy() ``` 这几个生命周期的意思是: ![图片](https://static001.geekbang.org/resource/image/1b/1b/1bdfff7c48055490ce06ab49eff5f01b.png?wh=1824x1110) 如果你不熟悉 Android Activity/Fragment 生命周期,那你可以看下[这篇文章](https://www.jianshu.com/p/1b3f829810a1)加深下理解。 那么如何让 Native Module 具备生命周期呢? React Native 给我们提供了一个接口:com.facebook.react.bridge.LifecycleEventListener。我们只需要在组件中添加这个接口的注册和取消注册,就可以让组件具备生命周期了。这里要注意,不要忘记在 onHostDestroy() 中移除注册,否则会造成内存泄漏。示例代码如下: ```plain public class TestJavaModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ReactModuleWithSpec, TurboModule { public TestJavaModule(ReactApplicationContext reactContext) { super(reactContext.real()); reactContext.addLifecycleEventListener(this); } @Override public String getName() { return getClass().getSimpleName(); } @Override public void onHostResume() { } @Override public void onHostPause() { } @Override public void onHostDestroy() { getReactApplicationContext().removeLifecycleEventListener(this); } } ``` 其实组件的生命周期原理很简单,就是观察者模式。当载体页触发自身的生命周期回调时,调用 ReactInstanceManager 的 onHostXXX() 方法,ReactInstanceManager 进而调用 ReactContext 的对应回调。 比如载体页调用 onResume() 时,最终会调用 ReactContext 的 onHostResume(),内部会遍历注册的事件进行回调: ```plain public void onHostResume(@Nullable Activity activity) { Iterator iterator = this.mLifecycleEventListeners.iterator(); while(iterator.hasNext()) { LifecycleEventListener listener = (LifecycleEventListener) iterator.next(); // 观察者模式,载体页时调用已注册组件的生命周期回调 listener.onHostResume(); } } ``` 以上就是 Android 端组件生命周期的讲解,我们再来看看 iOS 端。 ### iOS iOS中 NativeModules 组件的创建销毁时机,与bridge的创建销毁时机完全一致: * alloc:创建当前组件; * dealloc:销毁当前组件。 创建一个组件TestNativeModule,通过RCT\_EXPORT\_MODULE() 声明组件,默认会根据类名声明组件名,当然也可以通过在参数中传入其他字符串作为组件的名。 ```plain @implementation TestNativeModule RCT_EXPORT_MODULE() - (instancetype)init{   self = [super init];   return self; } - (void)dealloc{ NSLog(@"dealloc"); } ``` 而 **TurboModule** 组件的生命周期却与 NativeModule 不同。TurboModule 采用懒加载模式,在 Bridge 创建后页面中第一次 import 当前TurboModule ,也就是 JavaScript 端通过 **TurboModuleRegistry.getEnforcing**方法加载组件时, Native 会创建对应的 TurboModule 并进行缓存。如果 JS 端没有加载当前自定义组件,该组件就不会进行初始化。 JS 端加载组件方式如下: ```plain export default (TurboModuleRegistry.getEnforcing( 'TestTurboModule') : Spec); ``` 而TurboModule 的销毁时机与 Bridge 的销毁时机一致。 Bridge 进行销毁时会发送一个 RCTBridgeDidInvalidateModulesNotification 通知,TurboModuleManager会监听该事件,依次对所有已创建的 TurboModule 进行销毁。示例代码如下: ```plain - (void)bridgeDidInvalidateModules:(NSNotification *)notification {   RCTBridge *bridge = notification.userInfo[@"bridge"];   if (bridge != _bridge) {     return;   }   [self _invalidateModules];//销毁所有TurboModules } ``` 了解完了组件的生命周期,我们再来看下组件的传输数据类型。在组件运行过程中,Native 与 JavaScript 不可避免地需要进行数据交互,如 JavaScript 调用组件方法传入数据,Native 向 JavaScript 回传结果,而 React Native 也帮我们封装好了对应的数据类型。 ## 组件传输数据类型 在 Native 与 JavaScript 通信的过程中,组件需要获取输入参数、回传结果,对此 React Native 给我们包装了相应的数据类型,方便快速操作,我们通过一个Demo来简单了解下。 现在,我们让JavaScript端调用 TestModule 的 testMethod 方法,传入参数 type 和 message,接收 native 回传数据: ```plain NativeModules.TestModule.testMethod({type: 1, message: "fromJS"}, (result)=>{ console.info(result); } ); ``` 然后我们来看TestModule 在 Android、iOS 侧的实现。先来看 Android 端是怎么做的。 不过,在实现 TestModule 之前,我们需要先了解下 Android 端的组件传输数据类型: ![图片](https://static001.geekbang.org/resource/image/61/87/615f0162574e42c30557a1356472ee87.png?wh=744x924) 这里你要注意,数字类型有点特殊。因为 JavaScript 不支持 long 64 位长类型,只支持 int (32)和double,所以对于长数字,JavaScript端统一用 double 表示。那么 Android 端如何转换成自己需要的数据类型呢? 我们以 long 为例,可以这样参考[官方issue](https://github.com/facebook/react-native/issues/12506)这样处理: ```plain double value = readableMap.getDouble(key); try { // 判断是否为 long 的范围: 超过了 int 的最大值且为整数 if (value > Integer.MAX_VALUE && value % 1 == 0) { long cv = (long) value; // 转换成 long 型返回 } } catch (Exception e) { // 异常时,仍使用 double } ``` 这段代码中,我们先将 JavaScript 传入的数值统一以双精度浮点数 double 来获取。获取完后,判断这个值是否超出了整数的最大值且不为小数,条件符合就将它转换成长整数 long,否则还是以 double 来返回。 了解完 Android 端的组件数据传输类型后,我们就可以来实现上文中的 TestModule了: ```plain public class TestModule extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { public TestModule(ReactApplicationContext reactContext) { super(reactContext.real()); } @Override public String getName() { return getClass().getSimpleName(); } @ReactMethod public void testMethod(ReadableMap data, Callback callback) { // 获取 JS 的调用输入参数 int type = data.getInt("type"); String message = data.getString("message"); // 回传数据给 JS WritableMap resultMap = new WritableNativeMap(); map.putInt("code", 1); map.putString("message", "success"); callback.invoke(resultMap); } } ``` 上面代码中,我们定义了 Native 组件 TestModule,内部实现了 JavaScript 需要调用的 testMethod 方法。此方法包含两个参数:ReadableMap 和 Callback。ReadableMap 为 JavaScript 传入参数的字典,我们可以通过对应的 key 获取到 JavaScript 的入参值,而 Callback 是在 Native 回传数据时需要使用的,后面我们还有对通信方式这部分的讲解,这里我们只需要了解一下就好。 具体实现上,我们是首先获取 JavaScript 调用传入的 type 和 message,然后再通过 WritableMap 写入数据,最后通过 callback 回传给 JavaScript。 以上就是 Android 端的 React Native 读写数据类型,我们再来看下 iOS 端。 iOS端也是一样,在实现前面这个 demo 前,我们需要先看下 iOS 端支持的传入数据类型: ![图片](https://static001.geekbang.org/resource/image/af/f8/afbea1f2yyfa12fec0df7248913d97f8.png?wh=772x956) 那么,iOS端中是如何实现上文中的TestModule的呢?我们可以在Module中进行callback,然后通过NSArray来返回,如下: ```plain RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback){   if (!callback) {     return;   }   callback(@[ @"value from callback!" ]); } ``` 不过,我们这个组件案例,只是演示了 Native 可以通过 callback 向 JavaScript 回传数据。那么除了 callback,React Native 与原生还有什么通信方式呢? ## React Native 与原生的通信方式 总体来说,native 向 JavaScript 传递数据的方式分成以下三种: * Callback:由 JavaScript 主导触发,Native 进行回传,一次触发只能传递一次; * Promise:由 JavaScript 主导触发,Native 进行回传,一次触发只能传递一次。Promise 是 ES6 的新特性,类似 RXJava 的链式调用。Promise 有三种状态,分别是pending (进行时)、resolve (已完成)、reject (已失败); * 发送事件:由 Native 主导触发,可传递多次,类似 Android 的广播和 iOS 的通知中心。 Callback 在上面的例子中已经出现过了,我们通过 callback.invoke(xx) 就可以将数据回传给 JavaScript,使用起来比较简单,这边我们就不再赘述了。现在我们主要来看下 Promise 和发送事件的示例,以便更好地了解 React Native 和原生之间是如何进行通信的。 **首先我们来看下Promise 示例**,我们从 JavaScript 如何触发、Native 如何回传数据两方面来进行讲解。 首先,JavaScript 端调用客户端定义的 SystemPropsModule 的 getSystemModel 来获取手机的设备类型,获取结果的方式使用 Promise 方式 (then… catch…): ```plain NativeModules.SystemPropsModule.getSystemModel().then(result=> { console.log(result); }).catch(error=> { console.log(error); }); ``` 然后,Native 端定义 SystemPropsModule,实现 getSystemModel 方法,内部使用 promise 获取手机的 model 数据。使用 promise.reolve(xx) 为成功,promise.reject(xx) 为失败: ```plain SystemPropsModule: ... @ReactMethod public void getSystemModel(Promise promise) { // 回传成功,使用 resolve promise.resolve(Build.MODEL); } ... ``` **接下来看发送事件示例**,我们从 JavaScript 如何监听 Native 事件、Native 如何发送事件这两方面来进行讲解。 首先,JavaScript 端使用 EventEmitterManager 来注册 Native 的事件监听。通过 NativeModules 获取 EventEmitterManager,随后使用它构建出 NativeEventEmitter,最后通过 NativeEventEmitter 注册监听: ```plain componentWillMount(){ // 拿到原生模块 var eventEmitterManager = NativeModules.EventEmitterManager; const nativeEventEmitter = new NativeEventEmitter(eventEmitterManager); const eventEmitterManagerEvent = EventEmitterManager.EventEmitterManagerEvent; // 监听 Native 发送的通知 this.listener = nativeEventEmitter.addListener(eventEmitterManagerEvent, (data) => console.log("Receive native event: " + data); ); } componentWillUnmount(){ // 移除监听 this.listener.remove(); } ``` 在 Native 端的使用则很简单。我们获取 RCTDeviceEventEmitter 这个 JSModule,使用 emit 方法就可以向 JavaScript 发送事件了: ```plain reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("msg", "say hello"); ``` 自定义组件相关的知识点,我们先介绍到这里。接下来进入实战阶段,我们将分别以一个数据存取 TurboModule 和视频播放 Fabric component 的案例,加深你对自定义组件的理解。我们先来看TurboModule。 ## TurboModule:数据存取 TurboModule 采用懒加载模式,在运行时第一次 import 该 TurboModule 时, Native 会创建对应的 TurboModule 并进行缓存。而旧版本的 NativeModule 都是在创建环境时统一进行构造的,会对 React Native 的启动性能有比较大的影响。 接下来我们以一个实际的案例来带你了解 TurboModule。当你需要使用 native 数据存取相关能力,如跨进程存取、偏好存取、加密存取等,而 React Native 自带的数据存储 module 满足不了你的需求,你可以通过自定义数据存储的 TurboMoudle 来实现。我们先来看下 JavaScript 侧。 ### JavaScript 在 Spec 中定义方法,定义好存和取的方法后,再导出 StorageModule: ```plain export interface Spec extends TurboModule { +save: (key: string, value: string, callback: (value: Object) => void) => void; +get: (key: string, callback: (value: Object) => void) => void; } // 导出 StorageModule export default (TurboModuleRegistry.getEnforcing( 'StorageModule', ): Spec); ``` 调用: ```plain NativeModules.StorageModule.save("testKey", "testValue", (result)=>{ console.info(result); } ); NativeModules.StorageModule.get("testKey", (result)=>{ console.info(result); } ); ``` 接下来我们再看看 Android 端和 iOS 端的实现。 ### Android 在实现 StorageModule 之前,我们需要在混合工程中,将 React Native 新架构的运行配置搭建好,这套配置可以运行 TurboModule、Fabric,后面的 Fabric 案例也是基于此配置来运行的。 而且,上一讲在我们已经讲解了如何基于 React Native 最新版本(0.68.0)搭建混合应用,我们再这个基础上开启新架构配置就好了。 **第一步,我们要做些准备工作,也就是获取 newarchitecture 的模版代码。** 我们以 0.68.0 版本创建一个 React Native 工程,来获取新架构的模版代码,我们把这个工程名叫做ReactNativeNewArch: ```plain npx react-native init ReactNativeNewArch --version 0.68.0 ``` 创建好后,你将看到如下工程代码,包含 Java 和 C++: ![图片](https://static001.geekbang.org/resource/image/f6/3a/f64e5f3da0730b6a6a8928dc9736ea3a.png?wh=1164x1394) 这些工程代码主要是新架构的 JSI、Fabric、TurboModule 的注册和加载代码,内部逻辑非常复杂,这一讲我们就不做过多分析了,先专注于实操部分。 如果我们想基于这个 Demo 去运行新架构,可以做如下操作: ```plain 1. 修改 android 目录下的 gradle.properties: # 开启新架构 newArchEnabled=true # 配置 java home 为 JDK 11 org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home 2. 运行 yarn react-native run-android ``` **第二步,拷贝 newarchitecture 的模版代码到我们之前的混合工程。** 这一步中,我们需要将 Java 层和 C++ 层代码拷贝到混合工程中,需要拷贝的相关代码如下: ```plain Java 层: - MainComponentsRegistry.java - MainApplicationTurboModuleManagerDelegate.java C++ 层: - jni 目录 ``` 拷贝完的效果是这样的: ![图片](https://static001.geekbang.org/resource/image/b5/73/b51022154e4d1831428ced687c224173.png?wh=880x1076) **第三步是修改拷贝的代码,主要是下面这四点。** **1.MainApplicationTurboModuleManagerDelegate.java:** 修改 so 库名称为我们自定义名称 geektime\_new\_arch。 ```plain @Override protected synchronized void maybeLoadOtherSoLibraries() { if (!sIsSoLibraryLoaded) { SoLoader.loadLibrary("geektime_new_arch"); sIsSoLibraryLoaded = true; } } ``` **2.jni/Android.mk:**修改 Android.mk 中的 so 库名称为上面的 geektime\_new\_arch。 ```plain # You can customize the name of your application .so file here. LOCAL_MODULE := geektime_new_arch ``` **3.jni/MainApplicationTurboModuleManagerDelegate.h:**修改 MainApplicationTurboModuleManagerDelegate 对应的 Java 类路径,截图中拷贝好的 MainApplicationTurboModuleManagerDelegate.java 路径为 com/reactnativenewarch/newarchitecture/modules。 ```plain static constexpr auto kJavaDescriptor = "Lcom/reactnativenewarch/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;"; ``` **4.jni/MainComponentsRegistry.h:**修改 MainComponentsRegistry 对应的 Java 类路径,截图中拷贝好的 MainComponentsRegistry.java 路径为 com/reactnativenewarch/newarchitecture/components。 ```plain constexpr static auto kJavaDescriptor = "Lcom/reactnativenewarch/newarchitecture/components/MainComponentsRegistry;"; ``` **做完后,最后一步就是修改 React Native 初始化代码了。** 这里有两处要修改。第一处是设置 ReactPackageTurboModuleManagerDelegateBuilder 为上面的 MainApplicationTurboModuleManagerDelegate(TurboModule 用);第二处是设置 setJSIModulesPackage(Fabric 用,JSI 实现): ```plain public void initRN() { ReactInstanceManagerBuilder builder = ReactInstanceManager.builder() .setApplication((Application) getApplicationContext()) .addPackage(new MainReactPackage()) .setJSMainModulePath("index.android") .setInitialLifecycleState(LifecycleState.BEFORE_CREATE) .setReactPackageTurboModuleManagerDelegateBuilder(new MainApplicationTurboModuleManagerDelegate.Builder()) .setJSIModulesPackage(getJSIModulePackage()); ReactInstanceManager reactInstanceManager = builder.build(); } ``` 其中 getJSIModulePackage() 我特意摘出来放在了下面。这段代码实现需要从模版代码的 MainApplicationReactNativeHost 的 getJSIModulePackage 拷贝,内部调用了上面的 MainComponentsRegistry 进行注册: ```plain static JSIModulePackage getJSIModulePackage() { return new JSIModulePackage() { @Override public List getJSIModules( final ReactApplicationContext reactApplicationContext, final JavaScriptContextHolder jsContext) { final List specs = new ArrayList<>(); specs.add( new JSIModuleSpec() { @Override public JSIModuleType getJSIModuleType() { return JSIModuleType.UIManager; } @Override public JSIModuleProvider getJSIModuleProvider() { final ComponentFactory componentFactory = new ComponentFactory(); CoreComponentsRegistry.register(componentFactory); MainComponentsRegistry.register(componentFactory); List viewManagers = new ArrayList<>(); ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagers); return new FabricJSIModuleProvider( reactApplicationContext, componentFactory, new EmptyReactNativeConfig(), viewManagerRegistry); } }); return specs; } }; } ``` 至此,我们新架构的运行环境就配置好了。由于目前新架构文章非常少,几乎没有混合工程运行新架构的方案,上面这些主要是我们用了大量的时间去调研和测试的结果,你可以参考一下。 好了,回到正题。在混合工程中,新架构的运行环境搭建好后,我们就可以简单快速地来写 TurboModule 和 Fabric 了。 我们继续来进行数据存储的 Demo 在 Java 层定义并实现 StorageModule。Android 端我们使用 SharedPreferences 来实现轻量级偏好存取。这里要注意,你需要继承 ReactModuleWithSpec和TurboModule,具体代码如下: ```plain // 1. 定义 StorageModule,继承 ReactContextBaseJavaModule 类 // 实现 ReactModuleWithSpec & TurboModule 接口 public class StorageModule extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { // native 存储的 sp 文件名称 private static final String SP_NAME = "rn_storage"; // 返回给 JS 的结果码 private static final int CODE_SUCCESS = 1; private static final int CODE_ERROR = 2; public StorageModule(ReactApplicationContextWrapper reactContext) { super(reactContext); } // 返回 module 名称,一般以类名作为 module 名称 @Override public String getName() { return StorageModule.class.getSimpleName(); } // 定义供 JS 调用的存储数据方法,isBlockingSynchronousMethod 表示是否同步执行 @ReactMethod(isBlockingSynchronousMethod = true) public void save(String key, String value, Callback callback) { WritableMap result = new WritableNativeMap(); // 如果 js 传入的 key 为空,则回传失败码和信息 if (TextUtils.isEmpty(key)) { result.putInt("code", CODE_ERROR); result.putString("msg", "key is empty or null"); callback.invoke(result); return; } // 调用 native 的 sp 进行数据存储 SharedPreferences sp = getReactApplicationContext().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); sp.edit().putString(key, value).apply(); result.putInt("code", CODE_SUCCESS); result.putString("msg", "save success"); // 回传 js 告知存储成功 callback.invoke(result); } // 定义供 JS 调用的获取数据方法,isBlockingSynchronousMethod 表示是否同步执行 @ReactMethod(isBlockingSynchronousMethod = true) public void get(String key, Callback callback) { // 如果 js 传入的 key 为空,则回传失败码和信息 WritableMap result = new WritableNativeMap(); if (TextUtils.isEmpty(key)) { result.putInt("code", CODE_ERROR); result.putString("msg", "key is empty or null"); callback.invoke(result); return; } // 调用 native 的 sp 获取 key 对应的 value 值 SharedPreferences sp = getReactApplicationContext().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); String value = sp.getString(key, ""); result.putInt("code", CODE_SUCCESS); result.putString("data", value); // 将结果回传给 js callback.invoke(result); } } ``` 然后是注册组件,注意 Package 需要继承 TurboReactPackage: ```plain public class MyTurboModulePackage extends TurboReactPackage { @Override public NativeModule getModule(String name, ReactApplicationContext reactContext) { switch(name) { case "StorageModule": return new StorageModule(reactContext); break; default: return null; } } @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { //... } } ``` 接下来注册到 ReactInstanceManager (使用 reactInstanceManagerBuilder注册): ```plain ReactInstanceManagerBuilder builder = ReactInstanceManager.builder() .addPackage(new MyTurboModulePackage()); ... // 其他 RN 初始化配置 ReactInstanceManager reactInstanceManager = builder.build(); ``` 然后我们利用新架构提供的 Codegen,生成新架构需要的 native 代码。不过,在使用 codegen 之前,我们需要在项目中应用相关的插件: (1) 在工程根目录安装 react-native-gradle-plugin。 ```plain yarn add react-native-gradle-plugin ``` (2) 在工程根目路的 settings.gradle 中配置 react-native-gradle-plugin,使用复合构建引入。 ```plain include ':app' rootProject.name = "GeekTimeRNAndroid" includeBuild('./node_modules/react-native-gradle-plugin') ``` (3) 在 app/build.gradle 中应用插件。 ```plain apply plugin: "com.facebook.react" ``` 这样做后,工程的 gradle 任务中就会出现 generateCodegenArtifactsFromSchema task。 ```plain 配置好后,我们以后就可以使用 codegen 能力了,然后执行 generateCodegenArtifactsFromSchema,最后运行App就可以了。 ../gradlew generateCodegenArtifactsFromSchema ``` Android端的就是这样,现在我们看iOS端需要怎么做。 ### iOS 在iOS端中,首先我们要创建一个类并遵循一个协议 spec ,协议中包含注册的API声明。而且,该协议需要遵循 RCTBridgeModule 协议和 RCTTurboModule 协议,并且创建一个JSI。示例代码如下: ```plain //定义一个Spec协议 @protocol DataStorageTurboModuleSpec - (NSString *)getString:(NSString *)string; @end //JSI实现 namespace facebook { namespace react { class JSI_EXPORT DataStorageTurboModuleSpecJSI : public ObjCTurboModule {   public:   DataStorageTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms); }; } // namespace react } // namespace facebook //定义一些方法 namespace facebook {   namespace react {     static facebook::jsi::Value __hostFunction_DataStorageTurboModuleSpecJSI_getString(     facebook::jsi::Runtime &rt,     TurboModule &turboModule,     const facebook::jsi::Value *args,     size_t count){   return static_cast(turboModule)       .invokeObjCMethod(rt, VoidKind, "getString", @selector(getString:), args, count); }    DataStorageTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms)     : ObjCTurboModule(params)   { //MethodMetadata 第一个参数为0代表该方法有一个参数     methodMap_["getString"] = MethodMetadata{1, __hostFunction_DataStorageTurboModuleSpecJSI_getString};   } } } //TurboModule遵循Spec协议 @interface DataStorageTurboModule : NSObject @end ``` 之后,我们要在该类中注册组件名和API: ```plain //DataStorageTurboModule.mm @implementation DataStorageTurboModule RCT_EXPORT_MODULE() - (std::shared_ptr)getTurboModule:     (const facebook::react::ObjCTurboModule::InitParams &)params{ //指定JSI   return std::make_shared(params); } RCT_EXPORT_METHOD(getString:(NSString *)string){ NSLog(@""); } @end ``` 以上便是自定义一个 TurboModule的流程。其实定义 TurboModule 并不复杂,而且 Facebook 也提供了代码生成工具 codegen,比较复杂的是在混合工程中搭建新架构的运行环境。前面我们花了不少内容讲述如何在客户端开启新架构,接下来的 Fabric 组件介绍也将在新架构环境基础上进行讲解,接下来我们继续来看Fabric 自定义组件。 ## Fabric:视频播放 Fabric 对标旧框架的 UIManager。FabricUIManager 可以和 C++ 层直接进行通讯,解除了原有的 UIManager 依赖单个 bridge 的问题。有了 JSI 后,以前批量依赖 bridge 的 UI 操作,都可以同步执行到 C++ 层,性能得到大幅提升,特别是在列表快速滑动、复杂动画交互方面提升更加明显。 现在,我们以一个视频播放组件为例,讲讲如何定义 Fabric 组件。我们先来看下 JavaScript 端的实现。 ### JavaScipt JavaScript需要定义属性以及 API,并 export 组件。示例代码如下: ```plain type NativeProps = $ReadOnly<{| ...ViewProps, url?: string |}>; // 定义视频播放的属性,url 为视频地址 export type VideoViewType = HostComponent; // 定义视频播放的方法,包括开始播放、停止播放、暂停播放 interface NativeCommands { +callNativeMethodToPlayVideo: ( ) => void; +callNativeMethodToStopVideo: ( ) => void; +callNativeMethodToPauseVideo: ( ) => void; } //导出外部调用的命令,包括开始播放、停止播放、暂停播放 export const Commands: NativeCommands = codegenNativeCommands({ supportedCommands: ['callNativeMethodToPlayVideo'], supportedCommands: ['callNativeMethodToStopVideo'], supportedCommands: ['callNativeMethodToPauseVideo'], }); // 导出包装好的组件,其中 VideoView 为引入 Native 的组件 export default (codegenNativeComponent( 'VideoView', ): VideoViewType); ``` JavaScript 端使用该组件: ```plain // 导入 ViewView 组件和工具 import VideoView, { Commands as VideoViewCommands, } from './VideoNativeComponent'; // 外部调用此方法即可调用 ViewView 视频播放能力 export default function MyView(props: {}): React.Node { return (