React Native JSI实现RN与原生通信的示例代码
作者:RiverLi
什么是JSI
React Native JSI (JavaScript Interface) 可以使 JavaScript 和 原生模块 更快、更简单的通信。它也是React Native 新的架构体系中Fabric UI层 和 Turbo 模块的核心部分。
JSI有什么不同
JSI 移除了原生代码和JavaScript代码之间的桥接(bridge),同时也省去了两端相互调用时大量的JSON序列化和反序列化操作。JSI为原生和JS交互打开了新的大门。下面是一些JSI的特点:
- JavaScript Interface 允许我们向JavaScript 运行时注册方法。这些方法在js环境中可以通过 global对象获取并调用。
- 我们完全可以使用C++或者在iOS里使用OC ,在Android里使用Java实现这些注册方法。
- 原先使用bridge 的方式实现的原生模块可以通过增加一层C++,快速转化为通过JSI实现。
- 在iOS端实现非常简单,因为C++和OC 可以方便的实现混编。
- 在Android中,我们需要通过JNI 做一些转化。
- 这些方法可以是完全同步的,这意味着不必强制使用async。await。
在iOS中使用JSI
下面我们将一步一步的在iOS工程中使用JSI实现原生与JS的通信。
创建一个新的React Native 项目
npx react-native init jsiDemo
iOS端配置
在iOS项目目录中创建C++文件,example.h、 example.cpp。
example.h
#ifndef EXAMPLE_H #define EXAMPLE_H namespace facebook { namespace jsi { class Runtime; } } namespace example { void install(facebook::jsi::Runtime &jsiRuntime); } #endif /* EXAMPLE_H */ example.m #include "example.h" #include <jsi/jsi.h> using namespace facebook::jsi; using namespace std; namespace example { void install(Runtime &jsiRuntime) { auto helloWorld = Function::createFromHostFunction(jsiRuntime, PropNameID::forAscii(jsiRuntime, "helloWorld"), 0, [](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value { string helloworld = "helloworld"; return Value(runtime, String::createFromUtf8(runtime,helloworld)); }); jsiRuntime.global().setProperty(jsiRuntime, "helloWorld", move(helloWorld)); } }
在上面的代码中,我们使用 createFromHostFunction 方法创建了一个方法,并通过setProperty 方法将其注册到js运行时。
接下来,我们需要创建一个moudle, 在moudle中执行install 方法。
我们创建OC 文件,SimpleJsi.h , SimpleJsi.mm
SimpleJsi.h
#import <React/RCTBridgeModule.h> @interface SimpleJsi : NSObject <RCTBridgeModule> @property (nonatomic, assign) BOOL setBridgeOnMainQueue; @end
SimpleJsi.mm
#import "SimpleJsi.h" #import <React/RCTBridge+Private.h> #import <React/RCTUtils.h> #import <jsi/jsi.h> #import "example.h" #import <sys/utsname.h> using namespace facebook::jsi; using namespace std; @implementation SimpleJsi @synthesize bridge = _bridge; @synthesize methodQueue = _methodQueue; RCT_EXPORT_MODULE() + (BOOL)requiresMainQueueSetup { return YES; } - (void)setBridge:(RCTBridge *)bridge { _bridge = bridge; _setBridgeOnMainQueue = RCTIsMainQueue(); [self installLibrary]; } - (void)installLibrary { RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge; if (!cxxBridge.runtime) { /** * This is a workaround to install library * as soon as runtime becomes available and is * not recommended. If you see random crashes in iOS * global.xxx not found etc. use this. */ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ /** When refreshing the app while debugging, the setBridge method is called too soon. The runtime is not ready yet quite often. We need to install library as soon as runtime becomes available. */ [self installLibrary]; }); return; } example::install(*(facebook::jsi::Runtime *)cxxBridge.runtime); } @end
在 setBridge 方法中,我们调用了 example的 install 方法,完成方法的注册。
RN端配置
修改App.js
import React from 'react'; import type {Node} from 'react'; import {Text, View, Button} from 'react-native'; const App: () => Node = () => { const [result, setResult] = React.useState(); const press = () => { setResult(global.helloWorld()); }; return ( // eslint-disable-next-line react-native/no-inline-styles <View style={{backgroundColor: '#FFFFFF', height: '100%'}}> <View style={{height: '10%'}} /> <Button onPress={press} title="按钮" /> <Text>{'调用helloword:' + result}</Text> </View> ); }; export default App;
点击按钮之后,发现result的值为helloworld。
结果
上面我们实现了js 调用原生,但没有参数,接下来我们实现一个单参数的调用。
js调用带参数的原生方法
我们在example.cpp 的 install 方法中增加multiply方法的注册,从arguments 中取出入参。
auto multiply = Function::createFromHostFunction(jsiRuntime, PropNameID::forAscii(jsiRuntime, "multiply"), 2, [](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value { int x = arguments[0].getNumber(); int y = arguments[1].getNumber(); return Value(x * y); });
jsiRuntime.global().setProperty(jsiRuntime, "multiply", move(multiply));
然后修改App.js
import React from 'react'; import type {Node} from 'react'; import {Text, View, Button} from 'react-native'; const App: () => Node = () => { const [result, setResult] = React.useState(); const press = () => { setResult(global.multiply(2, 2)); }; return ( // eslint-disable-next-line react-native/no-inline-styles <View style={{backgroundColor: '#FFFFFF', height: '100%'}}> <View style={{height: '10%'}} /> <Button onPress={press} title="按钮" /> <Text>{'2*2 = ' + result}</Text> </View> ); }; export default App;
结果
原生调用JS
原生调用js主要通过jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "jsMethod").call(jsiRuntime);方法实现。
首先我们在js中增加一个js方法。我们修改App.js 在上面增加jsMethod 方法。
import React from 'react'; import type {Node} from 'react'; import {Text, View, Button} from 'react-native'; const App: () => Node = () => { const [result, setResult] = React.useState(); global.jsMethod = () => { alert('hello jsMethod'); }; const press = () => { setResult(global.multiply(2, 2)); }; return ( // eslint-disable-next-line react-native/no-inline-styles <View style={{backgroundColor: '#FFFFFF', height: '100%'}}> <View style={{height: '10%'}} /> <Button onPress={press} title="按钮" /> <Text>{'2*2 = ' + result}</Text> </View> ); }; export default App;
在原生端,我们假设在进入应用的时候触发调用js方法,我们修改AppDelegate中的applicationWillEnterForeground 方法。
- (void)applicationWillEnterForeground:(UIApplication *)application { SimpleJsi *jsi = [self.bridge moduleForName:@"SimpleJsi"]; [jsi calljs]; }
通过moduleForName方法获取SimpleJsi对象, 然后通过SimpleJsi中的calljs 方法。
- (void)calljs { RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge; Runtime &jsiRuntime = *(facebook::jsi::Runtime *)cxxBridge.runtime; jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "jsMethod").call(jsiRuntime); }
结果
原生调用带参数的JS方法
多参数调用和无参调用类似,只是在call方法后面增加了参数列表。
首先我们先在js侧定义方法,修改app.js
import React from 'react'; import type {Node} from 'react'; import {Text, View, Button} from 'react-native'; const App: () => Node = () => { const [result, setResult] = React.useState(); global.jsMethod = () => { alert('hello jsMethod'); }; global.jsMultiply = (x, y) => { alert('x * y = ' + x * y); }; const press = () => { setResult(global.multiply(2, 2)); }; return ( // eslint-disable-next-line react-native/no-inline-styles <View style={{backgroundColor: '#FFFFFF', height: '100%'}}> <View style={{height: '10%'}} /> <Button onPress={press} title="按钮" /> <Text>{'2*2 = ' + result}</Text> </View> ); }; export default App;
然后我们修改原生端的调用
AppDelegate.m
- (void)applicationWillEnterForeground:(UIApplication *)application { SimpleJsi *jsi = [self.bridge moduleForName:@"SimpleJsi"]; // [jsi calljs]; [jsi callJsMultiply:4 y:4]; }
SimpleJsi.m
- (void)callJsMultiply:(int)x y:(int) y { RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge; Runtime &jsiRuntime = *(facebook::jsi::Runtime *)cxxBridge.runtime; jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "jsMultiply").call(jsiRuntime, x, y); }
结果
在原生端调用js的函数参数
在原生中调用js参数中的函数,需要通过arguments[i].getObject(runtime).getFunction(runtime).call(runtime, value);
的方式触发。
首先我们在example.cpp 中新注册一个方法multiplyWithCallback
auto multiplyWithCallback = Function::createFromHostFunction(jsiRuntime, PropNameID::forAscii(jsiRuntime, "multiplyWithCallback"), 3, [](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value { int x = arguments[0].getNumber(); int y = arguments[1].getNumber(); //调用callback arguments[2].getObject(runtime).getFunction(runtime).call(runtime, x * y); return Value(); }); jsiRuntime.global().setProperty(jsiRuntime, "multiplyWithCallback", move(multiplyWithCallback));
在js侧进行调用, 修改app.js
import React from 'react'; import type {Node} from 'react'; import {Text, View, Button} from 'react-native'; const App: () => Node = () => { const [result, setResult] = React.useState(); global.jsMethod = () => { alert('hello jsMethod'); }; global.jsMultiply = (x, y) => { alert('x * y = ' + x * y); }; const press = () => { // setResult(global.multiply(2, 2)); global.multiplyWithCallback(4, 5, alertResult); }; const alertResult = res => { alert(res); }; return ( // eslint-disable-next-line react-native/no-inline-styles <View style={{backgroundColor: '#FFFFFF', height: '100%'}}> <View style={{height: '10%'}} /> <Button onPress={press} title="按钮" /> <Text>{'2*2 = ' + result}</Text> </View> ); }; export default App;
点击按钮之后,会调用multiplyWithCallback 将alertResult 方式传递给原生。
结果
总结
上面就是本文对JSI 的介绍,文中的代码,你可以在公众号回复 JSI 获取GitHub 下载地址。
问题
在RN Debug的情况下,global.xx 无法找到对应的方法,个人也没有头绪,如果你有方法解决,请联系我,非常感谢。
参考资料
https://blog.notesnook.com/getting-started-react-native-jsi/
reactnative.maxieewong.com/
https://github.com/react-native-community/discussions-and-proposals/issues/91
ospfranco.com/
到此这篇关于React Native JSI实现RN与原生通信的示例代码的文章就介绍到这了,更多相关React Native原生通信内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!