Native Platform: Native Components (Fabric)
Diese Anleitung befasst sich mit der Bereitstellung von nativen Windows-UIs für React Native durch die Implementierung einer Native Component für die Windows-Plattform. Für einen Überblick über die native Entwicklung unter Windows lesen Sie bitte zuerst Native Platform: Overview, bevor Sie diese Anleitung lesen.
Hinweis: Lesen Sie die Anleitung zu Native Components auf reactnative.dev für die Schritte zur Implementierung neuer Native Components für die Android- und iOS-Plattformen.
Architekturhinweis: Diese Anleitung folgt der Empfehlung, eine Fabric Native Component zu erstellen, um die neue Architektur von React Native zu unterstützen. Sie funktioniert *nicht* mit Apps der alten Architektur. Um React Native für Windows-Apps zu unterstützen, die auf die alte Architektur abzielen, lesen Sie Native Platform: Native Components (Paper). Weitere Informationen zu React Native-Architekturen in React Native für Windows finden Sie unter New vs. Old Architecture.
Überblick
Um die Windows-Unterstützung für eine Native Component zu implementieren, benötigen Sie Folgendes:
- Definieren Sie die API-Oberfläche für Ihre Native Component in TypeScript-Spezifikationsdateien.
- Verwenden Sie React Native für Windows' Native Codegen, um die TypeScript-Spezifikationsdateien zu verarbeiten und die C++-Header für den Windows-Code zu erstellen.
- Schreiben Sie den Windows C++-Code zur Implementierung der Component View, die von den generierten Headern spezifiziert wird.
- Verwenden Sie die Native Component in Ihrem JavaScript.
Schritt-für-Schritt-Anleitung
0. Einrichtung
Sie benötigen ein React Native-Bibliotheksprojekt, das mit Windows-Unterstützung initialisiert wurde.
Hinweis: Der Rest dieser Anleitung geht davon aus, dass Sie die Anleitung Native Plattform: Erste Schritte befolgt haben, um ein neues Bibliotheksprojekt namens
testlibeinzurichten.
1. Definieren Sie die API-Oberfläche in TypeScript
Die Standardvorlage für eine neue Bibliothek enthält kein Beispiel für eine Native Component, daher müssen wir eine erstellen. In dieser Anleitung implementieren wir eine Komponente, die nativen UI-Code verwendet, um ihre Kindkomponente innerhalb der Grenzen eines Kreises zu rendern und die Ecken auszublenden. Diese CircleMask könnte beispielsweise verwendet werden, um das quadratische Konto- oder Profilbild eines Benutzers als Kreis darzustellen.
Zuerst müssen wir die Schnittstelle der Komponente in einer neuen TypeScript-Spezifikationsdatei src\CircleMaskNativeComponent.ts erstellen.
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { ViewProps } from 'react-native';
export interface CircleMaskProps extends ViewProps {}
export default codegenNativeComponent<CircleMaskProps>('CircleMask');
Diese Spezifikationsdatei deklariert, dass React Native erwartet, dass jede Plattform eine Native Component namens CircleMask implementiert, die die Eigenschaften von CircleMaskProps unterstützt.
Hinweis: Jede Native Component-Spezifikationsdatei muss im Format
<componentName> + NativeComponent.tsbenannt sein, damit sie korrekt als Spezifikationsdatei und nicht nur als normale TypeScript-Datei in Ihrer Bibliothek erkannt wird.
2. Verwenden Sie den Native Library Codegen von React Native für Windows
Bevor wir den nativen C++-Code für unsere Fabric Native Component(s) implementieren können, müssen wir den Native Library Codegen von React Native für Windows ausführen, d. h. den Befehl codegen-windows, der die TypeScript-Spezifikationsdateien verarbeitet und C++-Header mit der API-Oberfläche generiert, die wir implementieren müssen.
Zuerst müssen wir sicherstellen, dass das codegenConfig-Objekt in der package.json-Datei unserer Bibliothek korrekt definiert ist.
"codegenConfig": {
"name": "RNTestlibSpec",
"type": "all",
"jsSrcsDir": "src",
"outputDir": {
"ios": "ios/generated",
"android": "android/generated"
},
"android": {
"javaPackageName": "com.testlib"
},
"includesGeneratedCode": true,
"windows": {
"namespace": "testlibCodegen",
"generators": [
"modulesWindows",
"componentsWindows"
],
"outputDirectory": "windows/testlib/codegen",
"separateDataTypes": true
}
}
Die Konfiguration wird teilweise mit anderen Plattformen geteilt, aber für Windows sind die relevanten Felder type, jsSrcsDirs und das windows-Objekt. Während die Standardkonfiguration korrekt eingerichtet war, um die Native Modules der Bibliothek zu unterstützen, mussten wir sie modifizieren, um unseren Bedarf an Native Components zu unterstützen.
Insbesondere haben wir, um das Codegen für Native Components zu aktivieren, "type: "modules" in "type": "all" geändert und das Array "generators": [ "modulesWindows", "componentsWindows" ] hinzugefügt.
Hinweis: Weitere Informationen zur Konfiguration von
codegenConfigfinden Sie unter codegen-windows Codegen Config.
Das Einzige, was wir jetzt noch tun müssen, ist, den codegen-windows-Befehl auszuführen mit
yarn react-native codegen-windows
Hinweis: Standardmäßig wird der Befehl
codegen-windowsbei jedem nativen Build automatisch ausgeführt. Auf diese Weise werden Änderungen an der API-Oberfläche in den TypeScript-Spezifikationsdateien in den generierten Headern reflektiert, wodurch sichergestellt wird, dass der native Code mit der erforderlichen API-Oberfläche der Komponente auf dem neuesten Stand bleibt.
Jetzt sollten wir einige Dateien im Codegen-Ausgabeverzeichnis des Projekts sehen, d. h. den Ordner windows\testlib\codegen, den wir in unserer Konfiguration angegeben haben. Codegen-Dateien für Native Components werden weiter unter dem Ordner react\components\RNTestlibSpec abgelegt. Speziell für unsere CircleMask-Komponente sollten wir eine ziemlich große CircleMask.g.h sehen (unten gekürzt).
/*
* This file is auto-generated from CircleMaskNativeComponent spec file in flow / TypeScript.
*/
// clang-format off
#pragma once
#include <NativeModules.h>
#ifdef RNW_NEW_ARCH
#include <JSValueComposition.h>
#include <winrt/Microsoft.ReactNative.Composition.h>
#include <winrt/Microsoft.UI.Composition.h>
#endif // #ifdef It will not work with Old Architecture apps.
#ifdef RNW_NEW_ARCH
namespace testlibCodegen {
REACT_STRUCT(CircleMaskProps)
struct CircleMaskProps : winrt::implements<CircleMaskProps, winrt::Microsoft::ReactNative::IComponentProps> {
// Implementation truncated
};
struct CircleMaskEventEmitter {
// Implementation truncated
};
template<typename TUserData>
struct BaseCircleMask {
// Implementation truncated
};
template <typename TUserData>
void RegisterCircleMaskNativeComponent(
winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder,
std::function<void(const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder&)> builderCallback) noexcept {
packageBuilder.as<winrt::Microsoft::ReactNative::IReactPackageBuilderFabric>().AddViewComponent(
L"CircleMask", [builderCallback](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept {
// Implementation truncated
});
}
} // namespace testlibCodegen
#endif // #ifdef RNW_NEW_ARCH
Der generierte Code enthält drei bemerkenswerte native Typen:
- Eine
CircleMaskProps-Struktur, die die spezifizierten Eigenschaften der TypeScript-CircleMaskProps-Schnittstelle erfasst. - Eine
CircleMaskEventEmitter-Struktur, damit die Komponente beliebige spezifizierte JavaScript-Ereignisse auslösen kann. - Eine
BaseCircleMask-Struktur, die als Basistyp für eineCircleMaskFabric Component View verwendet werden soll. - Eine Funktion
RegisterCircleMaskNativeComponentzum Registrieren derCircleMaskFabric Component View.
Beachten Sie auch die Verwendung von #ifdef RNW_NEW_ARCH, um sicherzustellen, dass diese Typen nur dann einbezogen werden, wenn die Bibliothek von Apps der neuen Architektur verwendet wird.
Hinweis: Eine nicht gekürzte Version dieser Datei finden Sie unter der
CircleMask.g.hdes Native Module Sample.
3. Implementieren Sie den Windows-C++-Code
Nach Abschluss des Codegen ist es an der Zeit, eine CircleMaskComponentView im Windows-Code zu implementieren. React Native für Windows Fabric Component Views werden in C++ implementiert und rendern UIs mithilfe der Microsoft::UI::Composition APIs, auch bekannt als Windows App SDK/WinUI 3 Visual Layer.
3.1 Implementierung der Fabric Component View
Um unsere neue Fabric Component View zu erstellen, müssen wir zwei neue Dateien erstellen (in unserem Beispiel CircleMask.h und CircleMask.cpp im Ordner windows\testlib).
#pragma once
#include "pch.h"
#ifdef RNW_NEW_ARCH
#include "codegen/react/components/RNTestlibSpec/CircleMask.g.h"
#include <winrt/Microsoft.ReactNative.Composition.Experimental.h>
#endif
namespace winrt::testlib::implementation {
void RegisterCircleMaskNativeComponent(
winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept;
#ifdef RNW_NEW_ARCH
struct CircleMaskComponentView : winrt::implements<CircleMaskComponentView, winrt::IInspectable>,
testlibCodegen::BaseCircleMask<CircleMaskComponentView> {
winrt::Microsoft::UI::Composition::Visual CreateVisual(
const winrt::Microsoft::ReactNative::ComponentView &view) noexcept override;
void Initialize(const winrt::Microsoft::ReactNative::ComponentView & /*view*/) noexcept override;
private:
winrt::Microsoft::ReactNative::ComponentView::LayoutMetricsChanged_revoker m_layoutMetricChangedRevoker;
winrt::Microsoft::UI::Composition::SpriteVisual m_visual{nullptr};
};
#endif // #ifdef RNW_NEW_ARCH
} // namespace winrt::testlib::implementation
#include "pch.h"
#include "CircleMask.h"
namespace winrt::testlib::implementation {
void RegisterCircleMaskNativeComponent(
winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept {
#ifdef RNW_NEW_ARCH
testlibCodegen::RegisterCircleMaskNativeComponent<CircleMaskComponentView>(
packageBuilder,
[](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &builder) {
// Turn off default border handling, as it overrides the Clip property of the visual and doesn't render
// correctly anyway This means we would have to implement drawing our own borders (which we don't do in this
// example)
builder.SetViewFeatures(
winrt::Microsoft::ReactNative::Composition::ComponentViewFeatures::Default &
~winrt::Microsoft::ReactNative::Composition::ComponentViewFeatures::NativeBorder);
});
#endif
}
#ifdef RNW_NEW_ARCH
winrt::Microsoft::UI::Composition::Visual CircleMaskComponentView::CreateVisual(
const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
auto compositor = view.as<winrt::Microsoft::ReactNative::Composition::ComponentView>().Compositor();
m_visual = compositor.CreateSpriteVisual();
auto ellipseGeometry = compositor.CreateEllipseGeometry();
auto clip = compositor.CreateGeometricClip();
clip.Geometry(ellipseGeometry);
m_visual.Clip(clip);
return m_visual;
}
void CircleMaskComponentView::Initialize(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
m_layoutMetricChangedRevoker = view.LayoutMetricsChanged(
winrt::auto_revoke,
[wkThis = get_weak()](
const winrt::IInspectable & /*sender*/, const winrt::Microsoft::ReactNative::LayoutMetricsChangedArgs &args) {
if (auto strongThis = wkThis.get()) {
auto visual = strongThis->m_visual;
// Turning off default border handling has the side-effect of also stopping the visual from being positioned,
// so unless that changes we have to position the visual ourselves
// See https://github.com/microsoft/react-native-windows/issues/14706
visual.Size(
{args.NewLayoutMetrics().Frame.Width * args.NewLayoutMetrics().PointScaleFactor,
args.NewLayoutMetrics().Frame.Height * args.NewLayoutMetrics().PointScaleFactor});
visual.Offset({
args.NewLayoutMetrics().Frame.X * args.NewLayoutMetrics().PointScaleFactor,
args.NewLayoutMetrics().Frame.Y * args.NewLayoutMetrics().PointScaleFactor,
0.0f,
});
auto ellipseGeometry = strongThis->m_visual.Clip()
.as<winrt::Microsoft::UI::Composition::CompositionGeometricClip>()
.Geometry()
.as<winrt::Microsoft::UI::Composition::CompositionEllipseGeometry>();
winrt::Windows::Foundation::Numerics::float2 radius = {
args.NewLayoutMetrics().Frame.Width * args.NewLayoutMetrics().PointScaleFactor / 2,
args.NewLayoutMetrics().Frame.Height * args.NewLayoutMetrics().PointScaleFactor / 2};
ellipseGeometry.Center(radius);
ellipseGeometry.Radius(radius);
}
});
}
#endif // #ifdef RNW_NEW_ARCH
} // namespace winrt::testlib::implementation
Wie Sie sehen können, definiert die Datei CircleMask.h zwei Dinge:
- Eine Funktion
RegisterCircleMaskNativeComponentzum Registrieren derCircleMaskFabric Component View bei React Native. - Eine Struktur
CircleMaskComponentView, die dieCircleMaskFabric Component View enthält.
Beides hängt von den Typen ab, die in der zuvor generierten Datei CircleMask.g.h bereitgestellt werden. Dann haben wir in CircleMask.cpp die Implementierungsdetails für unsere neue Native Component. Beachten Sie erneut die Verwendung von #ifdef RNW_NEW_ARCH, um sicherzustellen, dass der Code der Fabric Component View nur dann einbezogen wird, wenn die Bibliothek von Apps der neuen Architektur verwendet wird.
Hinweis: Ein umfassenderes Beispiel, wie eine
CircleMask-Komponente sowohl für Fabric als auch für Paper gleichzeitig implementiert wird, finden Sie in der Implementierung im Projekt Native Module Sample.
3.2 Hinzufügen der Native Component-Dateien zum nativen Projekt
Da wir einige neue native Dateien erstellt haben (oben CircleMask.h und CircleMask.cpp), müssen wir sicherstellen, dass sie im nativen Windows-Projekt (in unserem Beispiel windows\testlib\testlib.vcxproj und windows\testlib\testlib.vcxproj.filters) enthalten sind, damit sie im nativen Build berücksichtigt werden.
<ItemGroup>
<ClInclude Include="testlib.h" />
+ <ClInclude Include="CircleMask.h" />
<ClInclude Include="ReactPackageProvider.h">
<DependentUpon>ReactPackageProvider.idl</DependentUpon>
</ClInclude>
<ClInclude Include="resource.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="testlib.cpp" />
+ <ClCompile Include="CircleMask.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ReactPackageProvider.cpp">
<DependentUpon>ReactPackageProvider.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="targetver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="testlib.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="CircleMask.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="testlib.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="CircleMask.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
Hinweis: Alle von Codegen im Ordner
windows/testlib/codegenerstellten Header sind bereits enthalten und müssen hier nicht manuell hinzugefügt werden.
3.3 Registrieren der Fabric Component View beim React Package Provider
Jede React Native für Windows-Bibliothek enthält einen IReactPackageProvider, der alle nativen Module (und/oder Komponenten) der Bibliothek enthält, damit React Native sie zur Laufzeit verwenden kann. Der letzte Teil der nativen Arbeit, den wir benötigen, ist die Aktualisierung von ReactPackageProvider::CreatePackage in windows\testlib\ReactPackageProvider.cpp.
#include "pch.h"
#include "ReactPackageProvider.h"
#if __has_include("ReactPackageProvider.g.cpp")
#include "ReactPackageProvider.g.cpp"
#endif
#include "testlib.h"
#include "CircleMask.h"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::testlib::implementation {
void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept {
AddAttributedModules(packageBuilder, true);
RegisterCircleMaskNativeComponent(packageBuilder);
}
} // namespace winrt::testlib::implementation
Der entscheidende Punkt hier ist das Hinzufügen des Include-Befehls #include "CircleMask.h" und das Hinzufügen des Aufrufs der zuvor erstellten Funktion RegisterCircleMaskNativeComponent. Dies stellt sicher, dass die neue Native Component in das Paket der Bibliothek aufgenommen wird.
4. Verwenden Sie die Native Component in Ihrem JavaScript
Wenn wir nun zur TypeScript-Spezifikationsdatei CircleMaskNativeComponent.ts zurückkehren, sehen wir, dass sie die CircleMaskProps-Schnittstelle sowie die Native Component exportiert. Der nächste Schritt ist die Verwendung dieser exportierten Elemente in unserem JavaScript-Code.
Da der Zweck der Bibliothek darin besteht, die native Funktionalität für Code außerhalb der Bibliothek (auch bekannt als unser React Native für Windows-App-Code) bereitzustellen, ist die Standardeinstellung, die Funktionalität im Index der Bibliothek zu exportieren, in diesem Fall in src\index.tsx.
import Testlib from './NativeTestlib';
export function multiply(a: number, b: number): number {
return Testlib.multiply(a, b);
}
export {default as CircleMask} from './CircleMaskNativeComponent';
export * from './CircleMaskNativeComponent';
Wir sehen also, dass das testlib JavaScript-Modul einfach unsere neue Native Component als CircleMask exportiert und alles andere unverändert aus dem CircleMaskNativeComponent-Modul.
Hinweis: Bibliotheken sind nicht verpflichtet, ihre Native Components *direkt* an ihre Konsumenten weiterzugeben. Dieses Beispiel veranschaulicht nur den einfachsten Fall, nämlich die unveränderte Weitergabe der
CircleMaskNative Component. Bibliotheken können und verpacken ihre Native Components oft in JavaScript-Komponenten und bieten daher ihren Kunden eine völlig andere API-Oberfläche.
Nächste Schritte
Nachdem Sie Ihre native Bibliothek implementiert haben, ist der letzte Schritt die Nutzung in Ihrer React Native für Windows-App. Fahren Sie mit Native Plattform: Native Bibliotheken verwenden fort.