React Native 新架构简介
React Native 的新架构是为了解决旧架构中存在的性能瓶颈和开发体验问题而推出的重大升级。新架构主要包含以下几个核心组件:
- JSI (JavaScript Interface): 取代了旧架构中的 JSC (JavaScriptCore),提供了 JS 和原生代码之间的直接通信能力。
- Fabric: 新的渲染系统,通过 C++ 桥接层实现了更高效的 UI 渲染。
- TurboModules: 新的原生模块系统,允许更高效地调用原生 API。
- CodeGen: 自动代码生成工具,减少了手动编写样板代码的工作量。
- Hermes: Facebook 开发的专为移动应用优化的 JavaScript 引擎。
相比于旧架构,新架构最大的优势在于消除了异步桥接带来的性能损耗,允许 JavaScript 和原生代码直接通信,大幅提升了应用性能和开发体验。
JSI 是什么?
JSI (JavaScript Interface) 是 React Native 新架构中的核心组件之一,它是一个轻量级的 C++ API 层,提供了 JavaScript 和原生代码之间的直接通信能力。
与旧架构中的 Bridge 不同,JSI 允许:
- 同步调用: JavaScript 可以同步调用原生方法,无需通过异步消息队列。
- 引用共享: JavaScript 可以持有原生对象的引用,反之亦然。
- 引擎无关: JSI 设计为 JS 引擎无关的接口,可以兼容不同的 JS 引擎(如 JSC、Hermes 等)。
- 双向通信: 不仅 JS 可以调用原生代码,原生代码也可以直接调用 JS 函数。
这种设计解决了旧架构中的大量性能问题,特别是由于异步通信导致的延迟和序列化/反序列化开销。
RCTBridge 在新架构中的角色
在 React Native 的新架构中,RCTBridge
仍然存在,但其角色发生了变化。它主要承担以下职责:
- 兼容性层: 为了保证旧代码能在新架构中正常运行,
RCTBridge
提供了向后兼容的接口。
- 生命周期管理: 管理 JS 运行时的生命周期,包括初始化、加载 JS bundle 和销毁。
- 提供 JSI 访问: 通过
RCTCxxBridge
暴露 JSI 运行时,允许原生代码访问 JS 运行时。
- 线程管理: 维护专用的 JavaScript 线程,确保 JSI 操作的线程安全性。React Native 的架构中有明确的线程划分,包括 JavaScript 线程、主线程(UI 线程)和 Shadow 线程,
RCTBridge
通过 dispatchBlock:queue:
等方法在适当的线程上执行代码。
在新架构中,直接通信大多通过 JSI 实现,而 RCTBridge
则更多地充当一个容器和生命周期管理者的角色。特别是它的 RCTCxxBridge
子类,提供了从原生代码到 JSI 运行时的重要桥梁。
iOS 通过 JSI 调用 RN 函数的实现
接下来,我们将详细介绍如何在 iOS 端通过 JSI 调用 React Native 中定义的 JavaScript 函数。
1. 配置 AppDelegate
首先,我们需要在 AppDelegate 中进行必要的配置,包括创建 RCTBridge 实例和注册通知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import UIKit import React import CocoaLumberjack
@main class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var bridge: RCTBridge?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { bridge = RCTBridge(delegate: self, launchOptions: launchOptions) NotificationCenter.default.addObserver(self, selector: #selector(onJSDidLoad), name: NSNotification.Name("RCTJavaScriptDidLoadNotification"), object: nil) return true } }
extension AppDelegate: RCTBridgeDelegate { func sourceURL(for bridge: RCTBridge) -> URL? { #if DEBUG let jsCodeLocation = URL(string: "http://localhost:8081/index.bundle?platform=ios") print("React Native 开发服务器 URL: \(jsCodeLocation?.absoluteString ?? "无效URL")") return jsCodeLocation #else guard let bundleURL = Bundle.main.url(forResource: "main", withExtension: "jsbundle") else { print("错误: 无法找到 main.jsbundle 文件") return nil } return bundleURL #endif } }
@objc extension AppDelegate { func onJSDidLoad() { print("JS 加载完成") let hasSayHello = JSIBridge.hasJavaScriptFunction("sayHello") print("sayHello 函数是否存在: \(hasSayHello)") } }
|
这段配置代码包含以下关键部分:
- 创建 RCTBridge 实例:在应用启动时创建 RCTBridge,这是连接原生代码和 JavaScript 的桥梁。
- 注册通知:注册
RCTJavaScriptDidLoadNotification
通知,以便在 JavaScript 加载完成后执行相应的操作。
- 实现 RCTBridgeDelegate:提供 JavaScript bundle 的加载路径,区分开发环境和生产环境。
- JavaScript 加载完成的处理:在
onJSDidLoad
方法中处理 JavaScript 环境准备就绪后的逻辑。
这些配置是使用 JSI 调用 JavaScript 函数的前提,确保了 JavaScript 环境的正确初始化和加载。
2. 创建 JSIBridge 类
首先,我们需要创建一个桥接类,用于封装 JSI 调用的具体实现。这里我们创建了 JSIBridge
类:
1 2 3 4 5 6 7 8 9 10 11
| @interface JSIBridge : NSObject
+ (BOOL)hasJavaScriptFunction:(NSString *)functionName;
+ (NSString *)callJavaScriptFunction:(NSString *)functionName withArgument:(NSString *)argument;
@end
|
这个类提供了两个主要方法:
hasJavaScriptFunction
: 检查指定名称的 JavaScript 全局函数是否存在
callJavaScriptFunction:withArgument
: 调用指定名称的 JavaScript 全局函数,并传递一个字符串参数
3. 实现 JSIBridge 类
接下来,我们在 JSIBridge.mm 文件中实现这些方法。由于需要直接访问 JSI 接口,我们使用 Objective-C++ 编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #import "JSIBridge.h" #include <jsi/jsi.h> #import <React/RCTBridge+Private.h> #import <React/RCTBridge.h>
using namespace facebook::jsi; using namespace std;
@implementation JSIBridge
+ (RCTBridge *)getBridge { return [RCTBridge currentBridge]; }
+ (BOOL)hasJavaScriptFunction:(NSString *)functionName { RCTBridge *bridge = [self getBridge]; __block BOOL hasFunction = NO; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [bridge dispatchBlock:^{ RCTCxxBridge* cxxbridge = (RCTCxxBridge*)bridge; if (cxxbridge.runtime) { Runtime& runtime = *(Runtime*)cxxbridge.runtime; const char *name = functionName.UTF8String; try { Function function = runtime.global().getPropertyAsFunction(runtime, name); hasFunction = function.isFunction(runtime); } catch (JSError& error) { hasFunction = NO; } } dispatch_semaphore_signal(semaphore); } queue:RCTJSThread]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return hasFunction; }
+ (NSString *)callJavaScriptFunction:(NSString *)functionName withArgument:(NSString *)argument { RCTBridge *bridge = [self getBridge]; __block NSString *result = @""; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [bridge dispatchBlock:^{ RCTCxxBridge* cxxbridge = (RCTCxxBridge*)bridge; if (cxxbridge.runtime) { Runtime& runtime = *(Runtime*)cxxbridge.runtime; Function function = runtime.global().getPropertyAsFunction(runtime, functionName.UTF8String); Value param = Value(runtime, String::createFromUtf8(runtime, argument.UTF8String)); Value jsResult = function.call(runtime, std::move(param), 1); string str = jsResult.asString(runtime).utf8(runtime); result = [NSString stringWithUTF8String:str.c_str()]; } dispatch_semaphore_signal(semaphore); } queue:RCTJSThread]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return result; }
@end
|
这段代码的核心逻辑包括:
- 获取当前 RCTBridge 实例
- 从 RCTBridge 中获取 RCTCxxBridge,它提供了对 JSI 运行时的访问
- 通过 JSI API 获取全局函数对象并调用它
- 使用信号量确保 JS 线程执行完毕后再返回结果(同步调用)
4. 在 JavaScript 中定义函数
在 React Native 的 JavaScript 代码中,我们需要定义全局函数,使其可以被原生代码调用。以下是一个简单的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
global.sayHello = (name) => { return `Hello, ${name}! From React Native.`; };
global.getAppVersion = () => { return "1.0.0"; };
global.getUserInfo = () => { const store = require('./store').default; const state = store.getState(); return JSON.stringify(state.user); };
|
5. 在原生代码中调用 JavaScript 函数
现在,我们可以在任何原生代码中使用 JSIBridge 调用 JavaScript 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import Foundation
class MyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let hasSayHello = JSIBridge.hasJavaScriptFunction("sayHello") print("sayHello 函数存在: \(hasSayHello)") if hasSayHello { let result = JSIBridge.callJavaScriptFunction("sayHello", withArgument: "iOS") print("调用结果: \(result)") } if JSIBridge.hasJavaScriptFunction("getAppVersion") { let version = JSIBridge.callJavaScriptFunction("getAppVersion", withArgument: "") print("应用版本: \(version)") } } }
|
RCTBridge 在新架构中的实际应用
在上面的代码中,我们可以看到 RCTBridge 在新架构中的几个关键应用:
- 提供 JavaScript 运行时访问:通过
RCTCxxBridge
的 runtime
属性,我们可以获取 JSI 运行时,进而调用 JavaScript 函数。
- 管理 JavaScript 线程:使用
dispatchBlock:queue:
方法在 JavaScript 线程上执行代码,确保线程安全。
- 生命周期通知:通过
RCTJavaScriptDidLoadNotification
通知,我们可以知道 JavaScript 环境何时准备就绪。
- 提供 bundle 加载机制:通过
RCTBridgeDelegate
协议,我们可以控制 JavaScript bundle 的加载方式。
虽然在新架构中,直接通信主要通过 JSI 实现,但 RCTBridge 仍然在初始化阶段和生命周期管理中发挥着重要作用,是连接新旧架构的重要桥梁。
注意事项和最佳实践
在使用 JSI 进行原生与 JavaScript 通信时,需要注意以下几点:
- 线程安全: JSI 调用必须在 JavaScript 线程上执行,否则会导致崩溃。上面的代码通过
dispatchBlock
和信号量确保了线程安全。
- 错误处理: 调用 JavaScript 函数时可能会抛出异常,需要适当处理这些异常。
- 内存管理: JSI 允许原生代码和 JavaScript 互相持有对方的引用,需要注意避免循环引用导致的内存泄漏。
- 性能考虑: 虽然 JSI 提供了同步调用能力,但对于耗时操作,仍然应该考虑异步处理,避免阻塞主线程。
- 兼容性: 在混合使用新旧架构的过渡期,需要考虑兼容性问题。
扩展:支持更复杂的参数和返回值
上面的示例只演示了如何传递和返回简单的字符串参数,在实际应用中,我们可能需要处理更复杂的数据类型。以下是一个扩展版本,支持 JSON 对象的传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| + (id)callJavaScriptFunctionWithJSON:(NSString *)functionName jsonArgument:(NSDictionary *)argument { NSError *error; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:argument options:0 error:&error]; if (error) { NSLog(@"JSON序列化错误: %@", error); return nil; } NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; NSString *resultString = [self callJavaScriptFunction:functionName withArgument:jsonString]; NSData *resultData = [resultString dataUsingEncoding:NSUTF8StringEncoding]; id resultObject = [NSJSONSerialization JSONObjectWithData:resultData options:0 error:&error]; if (error) { return resultString; } return resultObject; }
|
总结
通过 JSI,React Native 的新架构为原生代码和 JavaScript 之间的通信提供了更直接、更高效的方式。本文介绍了如何在 iOS 端通过 JSI 调用 React Native 中定义的 JavaScript 函数,包括:
- React Native 新架构的核心组件和优势
- JSI 的基本概念和特性
- RCTBridge 在新架构中的角色变化
- 实现 JSI 调用的具体步骤和示例代码
- 使用过程中的注意事项和最佳实践
这种方法不仅提高了应用性能,还简化了原生与 JavaScript 之间的交互流程,为开发更复杂的混合应用提供了强大支持。
参考资料