原生模块 (高级)
需要进行架构审查:本文档旨在支持针对 React Native 的“旧”或“遗留”架构的开发。它可能适用也可能不适用于新架构的开发,需要审查并可能需要更新。有关 React Native Windows 中 React Native 架构的信息,请参阅新旧架构。
有关 Windows 上原生开发的最新信息,请参阅原生平台:概述。
本文档和底层平台代码仍在开发中。 示例(C# 和 C++/WinRT):
不使用特性编写原生模块 (C#)
原生模块页面描述了如何使用基于特性的方法在 C# 和 C++ 中编写原生模块。基于特性的方法使您编写原生模块变得容易,因为它使用反射来帮助 React Native 理解您的原生模块。
在极少数情况下,您可能需要直接针对 ABI 编写原生模块而无需使用反射。高级步骤保持不变
- 编写您的原生模块
- 注册您的原生模块
- 使用您的原生模块
但是,您的应用程序将负责处理特性本应提供的代码。本指南将逐步介绍这些步骤。
1. 编写您的原生模块
这是相同的 FancyMath
原生模块,但我们不使用自定义特性,而是将自己实现其成员的注册而无需使用反射。
FancyMath.cs
:
using System;
using Microsoft.ReactNative.Managed;
namespace NativeModuleSample
{
class FancyMath
{
public double E = Math.E;
public double PI = Math.PI;
public double Add(double a, double b)
{
return a + b;
}
}
}
2. 注册您的原生模块
这是我们的 FancyMathPackageProvider
,我们在其中手动注册原生模块的成员。
FancyMathPackageProvider.cs
:
namespace NativeModuleSample
{
public sealed class FancyMathPackageProvider : IReactPackageProvider
{
public void CreatePackage(IReactPackageBuilder packageBuilder)
{
packageBuilder.AddModule("FancyMath", (IReactModuleBuilder moduleBuilder) => {
var module = new FancyMath();
moduleBuilder.AddConstantProvider((IJSValueWriter writer) => {
writer.WritePropertyName("E");
writer.WriteDouble(module.E);
writer.WritePropertyName("Pi");
writer.WriteDouble(module.PI);
});
moduleBuilder.AddMethod("add", MethodReturnType.Callback,
(IJSValueReader inputReader,
IJSValueWriter outputWriter,
MethodResultCallback resolve,
MethodResultCallback reject) => {
double a = inputReader.GetNextArrayItem() ? inputReader.GetDouble() : throw new Exception();
double b = inputReader.GetNextArrayItem() ? inputReader.GetDouble() : throw new Exception();
double result = module.Add(a, b);
outputWriter.WriteArrayBegin();
outputWriter.WriteDouble(result);
outputWriter.WriteArrayEnd();
resolve(outputWriter);
});
return module;
});
}
}
}
如您所见,直接使用 API 是可能的,但代码看起来有点复杂。您负责创建自己的常量提供程序,将每个常量序列化到 IJSValueWriter
中。对于方法,您还负责反序列化参数、调用您的代码,然后序列化任何返回值。有一些(反)序列化帮助方法可以使它稍微简单一些
FancyMathPackageProvider.cs
:
namespace NativeModuleSample
{
public sealed class FancyMathPackageProvider : IReactPackageProvider
{
public void CreatePackage(IReactPackageBuilder packageBuilder)
{
packageBuilder.AddModule("FancyMath", (IReactModuleBuilder moduleBuilder) => {
var module = new FancyMath();
moduleBuilder.AddConstantProvider((IJSValueWriter writer) => {
writer.WriteProperty("E", module.E);
writer.WriteProperty("Pi", module.PI);
});
moduleBuilder.AddMethod("add", MethodReturnType.Callback,
(IJSValueReader inputReader,
IJSValueWriter outputWriter,
MethodResultCallback resolve,
MethodResultCallback reject) => {
double[] args;
inputReader.ReadArgs(out args[0], out args[1]);
double result = module.Add(args[0], args[1]);
outputWriter.WriteArgs(result);
resolve(outputWriter);
});
return module;
});
}
}
}
通过隐藏值读取器和写入器接口的使用,可以进一步简化代码
FancyMathPackageProvider.cs
:
namespace NativeModuleSample
{
public sealed class FancyMathPackageProvider : IReactPackageProvider
{
public void CreatePackage(IReactPackageBuilder packageBuilder)
{
packageBuilder.AddModule("FancyMath", (IReactModuleBuilder moduleBuilder) => {
var module = new FancyMath();
moduleBuilder.AddConstantProvider(() => new Dictionary<string, object> {
["E"] = module.E,
["Pi"] = module.PI
});
moduleBuilder.AddMethod("add", MethodReturnType.Callback, (MethodCallContext callContext) => {
double result = module.Add((double)callContext.Args[0], (double)callContext.Args[1]);
callContext.resolve(result);
});
return module;
});
}
}
}
这段代码看起来好多了,但是我们每次调用都会产生值装箱的开销,这涉及内存分配。我们使用 LINQ 表达式进行的代码生成避免了这种额外的开销。不过,反射和代码的初始使用也会有一些损失。从维护的角度来看,带特性的代码更容易支持,因为我们不需要在两个不同的地方描述相同的东西。
至于代码的其余部分,一旦我们有了 IReactPackageProvider
,注册该包与上面使用自定义特性的示例相同。
3. 在 JS 中使用您的原生模块
在 JS 中使用您的原生模块与使用特性定义原生模块的方式完全相同。
NativeFancyMath.ts
:
import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getConstants: () => {
E: number,
PI: number,
|};
add(a: number, b: number): Promise<number>;
}
export default TurboModuleRegistry.get<Spec>(
'FancyMath'
) as Spec | null;
Sample.js
:
import React, { Component } from 'react';
import {
AppRegistry,
Alert,
Text,
View,
} from 'react-native';
import FancyMath from './NativeFancyMath';
class NativeModuleSample extends Component {
_onPressHandler() {
FancyMath.add(
/* arg a */ FancyMath.getConstants().Pi,
/* arg b */ FancyMath.getConstants().E,
/* callback */ function (result) {
Alert.alert(
'FancyMath',
`FancyMath says ${FancyMath.getConstants().Pi} + ${FancyMath.getConstants().E} = ${result}`,
[{ text: 'OK' }],
{cancelable: false});
});
}
render() {
return (
<View>
<Text>FancyMath says PI = {FancyMath.getConstants().Pi}</Text>
<Text>FancyMath says E = {FancyMath.getConstants().E}</Text>
<Button onPress={this._onPressHandler} title="Click me!"/>
</View>);
}
}
AppRegistry.registerComponent('NativeModuleSample', () => NativeModuleSample);
带自定义事件发射器的原生模块
默认情况下,原生模块共享一个公共的 RCTDeviceEventEmitter
,它将实际事件发射到 JavaScript 中。然而,这带来了一个限制,即所有原生模块必须具有全局唯一的事件名称。如果您想确保您的事件名称不与其他原生模块的事件名称冲突,您可以创建自己的 EventEmitter
。
假设我们有 FancyMath
模块,其中我们已将 "MathEmitter"
指定为 EventEmitter
的名称
FancyMath.cs
:
using System;
using Microsoft.ReactNative.Managed;
namespace NativeModuleSample
{
[ReactModule(EventEmitterName = "MathEmitter")]
class FancyMath
{
[ReactConstant]
public double E = Math.E;
[ReactConstant("Pi")]
public double PI = Math.PI;
[ReactMethod("add")]
public double Add(double a, double b)
{
double result = a + b;
AddEvent(result);
return result;
}
[ReactEvent]
public ReactEvent<double> AddEvent { get; set; }
}
}
现在,当原生代码调用 AddEvent
时,它将基本上转换为 MathEmitter.emit("AddEvent", result)
的 JS 调用。
因此,为了使其工作,您需要创建并注册一个 MathEmitter
模块。您可以通过现有 EventEmitter
类来创建模块。
MathEmitter.js
:
const EventEmitter = require("EventEmitter");
const BatchedBridge = require("BatchedBridge");
BatchedBridge.registerLazyCallableModule("MathEmitter", () => {
return new EventEmitter();
});
ReactContext
方法的 C# 原生模块
带初始化器和访问 如果您的原生模块需要在原生 (C#) 端执行一些初始化逻辑,那么当应用程序设置时,有一种简单的方法可以实现。您只需要添加一个接受 ReactContext
并具有 [ReactInitializer]
特性的方法即可。如果您的原生模块需要定期执行某些操作,您可以通过在模块初始化期间设置计时器来实现,如以下示例所示
[ReactModule]
internal sealed class NativeModuleSample
{
private ThreadPoolTimer m_timer;
[ReactInitializer]
public void Initialize(ReactContext reactContext)
{
m_timer = ThreadPoolTimer.CreatePeriodicTimer(
new TimerElapsedHandler((timer) =>
{
// Do something every 5 seconds
}),
TimeSpan.FromSeconds(5)
);
}
~NativeModuleSample()
{
_timer?.Cancel();
}
}
如果您的模块需要访问 ReactContext
上下文或 ReactNativeHost
,您可以保留传递给标记为 [ReactInitializer]
的方法的上下文。
[ReactModule]
internal sealed class NativeModuleSample
{
private ReactContext m_reactContext;
[ReactInitializer]
public void Initialize(ReactContext reactContext)
{
m_reactContext = reactContext;
}
[ReactMethod]
public Task SampleAccessToHost()
{
var reactNativeHost = ReactNativeHost.FromContext(m_reactContext.Handle);
// Use debugging api that reloads the instance
reactNativeHost.ReloadInstance()
}
[ReactMethod]
public Task EmitRCTDeviceEvent()
{
m_reactContext.EmitJSEvent("RCTDeviceEventEmitter", "MyCustomJsEvent", 42);
}
}