原生平台:原生组件 (Paper)
本指南涵盖了通过为 Windows 平台实现一个原生组件来将原生的 Windows UI 暴露给 React Native。在阅读本指南之前,请参阅原生平台:概述,以获取 Windows 上原生开发的高级概述。
注意: 有关同时为 Android 和 iOS 平台实现新原生组件的步骤,请参阅 reactnative.dev 原生组件指南。
架构说明: 本指南展示了如何创建一个 Paper 原生组件来支持 React Native 的旧架构。它将不适用于新架构应用程序。要支持针对新架构的 React Native for Windows 应用程序,请参阅原生平台:原生组件 (Fabric)。有关 React Native for Windows 中 React Native 架构的更多信息,请参阅新旧架构对比。
高级概述
为了实现对原生组件的 Windows 支持,您需要
- 在 TypeScript 规范文件中定义原生组件的 API 表面
- 使用 React Native for Windows 的原生库代码生成器来获取 TypeScript 规范文件并创建 Windows 代码的 C++ 头文件
- 编写 Windows C++ 代码来实现 View Manager
- 在 JavaScript 中使用原生组件
分步指南
0. 设置
您需要一个已初始化并支持 Windows 的 React Native 库项目。
注意: 本指南的其余部分假设您已按照 原生平台:入门 指南设置了一个名为
testlib
的新库项目。
1. 在 TypeScript 中定义 API 接口
新库的默认模板不包含原生组件的示例,所以我们需要自己创建一个。在本指南中,我们将实现一个组件,它使用原生 UI 代码强制其子组件在圆形边界内渲染,遮盖住角落。这个 CircleMask
可以用来将用户的方形帐户或个人资料图片渲染成圆形。
首先,我们需要在新的 TypeScript 规范文件 src\CircleMaskNativeComponent.ts
中创建组件的接口
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { ViewProps } from 'react-native';
export interface CircleMaskProps extends ViewProps {}
export default codegenNativeComponent<CircleMaskProps>('CircleMask');
此规范文件声明 React Native 期望每个平台都实现一个名为 CircleMask
的原生组件,该组件支持 CircleMaskProps
的属性。
注意: 每个原生组件规范文件必须以
<componentName> + NativeComponent.ts
的格式命名,以便正确识别为规范文件,而不仅仅是库中的常规 TypeScript 文件。
2. 使用适用于 Windows 的 React Native 的原生库代码生成器
在制作 Fabric 原生组件时,React Native for Windows 的原生库代码生成器用于生成大量 C++ 代码来帮助您实现原生组件。遗憾的是,此生成的代码对 Paper 原生组件的实现没有帮助。有关更多详细信息,请参阅原生平台:原生组件 (Fabric)。
3. 实现 Windows C++ 代码
现在是时候在 Windows 代码中实现 CircleMaskViewManager
了。React Native for Windows Paper View Manager 用 C++ 实现,并使用 Windows::UI::Xaml
API(也称为 UWP XAML)渲染 UI。
3.1 实现 Paper View Manager
为了创建新的 Paper View Manager,我们需要创建两个新文件(对于我们的示例,在 windows\testlib
文件夹中的 CircleMask.h
和 CircleMask.cpp
)
#pragma once
#include "pch.h"
#ifndef RNW_NEW_ARCH
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Data.h>
#endif
namespace winrt::testlib::implementation {
void RegisterCircleMaskNativeComponent(
winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept;
#ifndef RNW_NEW_ARCH
struct CircleMaskViewManager : winrt::implements<
CircleMaskViewManager,
winrt::Microsoft::ReactNative::IViewManager,
winrt::Microsoft::ReactNative::IViewManagerWithChildren> {
public:
CircleMaskViewManager() {}
// IViewManager
winrt::hstring Name() noexcept;
winrt::Windows::UI::Xaml::FrameworkElement CreateView() noexcept;
// IViewManagerWithChildren
void AddView(
winrt::Windows::UI::Xaml::FrameworkElement const &parent,
winrt::Windows::UI::Xaml::UIElement const &child,
int64_t /*index*/) noexcept;
void RemoveAllChildren(winrt::Windows::UI::Xaml::FrameworkElement const &parent) noexcept;
void RemoveChildAt(winrt::Windows::UI::Xaml::FrameworkElement const &parent, int64_t /*index*/) noexcept;
void ReplaceChild(
winrt::Windows::UI::Xaml::FrameworkElement const &parent,
winrt::Windows::UI::Xaml::UIElement const & /*oldChild*/,
winrt::Windows::UI::Xaml::UIElement const &newChild) noexcept;
};
struct HeightToCornerRadiusConverter
: winrt::implements<HeightToCornerRadiusConverter, winrt::Windows::UI::Xaml::Data::IValueConverter> {
public:
HeightToCornerRadiusConverter() {}
winrt::Windows::Foundation::IInspectable Convert(
winrt::Windows::Foundation::IInspectable const &value,
winrt::Windows::UI::Xaml::Interop::TypeName const & /*targetType*/,
winrt::Windows::Foundation::IInspectable const & /*parameter*/,
winrt::hstring const & /*language*/) noexcept;
winrt::Windows::Foundation::IInspectable ConvertBack(
winrt::Windows::Foundation::IInspectable const &value,
winrt::Windows::UI::Xaml::Interop::TypeName const & /*targetType*/,
winrt::Windows::Foundation::IInspectable const & /*parameter*/,
winrt::hstring const & /*language*/) noexcept;
static winrt::Windows::UI::Xaml::Data::IValueConverter Instance() noexcept;
// IValueConverter
};
#endif // #ifndef 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 {
#ifndef RNW_NEW_ARCH
packageBuilder.AddViewManager(L"CircleMaskViewManager", []() { return winrt::make<CircleMaskViewManager>(); });
#endif
}
#ifndef RNW_NEW_ARCH
// IViewManager
winrt::hstring CircleMaskViewManager::Name() noexcept {
return L"CircleMask";
}
winrt::Windows::UI::Xaml::FrameworkElement CircleMaskViewManager::CreateView() noexcept {
auto const &view = winrt::Windows::UI::Xaml::Controls::Border();
auto const &binding = winrt::Windows::UI::Xaml::Data::Binding();
binding.Source(view);
binding.Path(winrt::Windows::UI::Xaml::PropertyPath(L"Height"));
binding.Converter(HeightToCornerRadiusConverter::Instance());
view.SetBinding(winrt::Windows::UI::Xaml::Controls::Border::CornerRadiusProperty(), binding);
return view;
}
// IViewManagerWithChildren
void CircleMaskViewManager::AddView(
winrt::Windows::UI::Xaml::FrameworkElement const &parent,
winrt::Windows::UI::Xaml::UIElement const &child,
int64_t /*index*/) noexcept {
if (auto const &border = parent.try_as<winrt::Windows::UI::Xaml::Controls::Border>()) {
border.Child(child);
}
}
void CircleMaskViewManager::RemoveAllChildren(winrt::Windows::UI::Xaml::FrameworkElement const &parent) noexcept {
if (auto const &border = parent.try_as<winrt::Windows::UI::Xaml::Controls::Border>()) {
border.Child(nullptr);
}
}
void CircleMaskViewManager::RemoveChildAt(
winrt::Windows::UI::Xaml::FrameworkElement const &parent,
int64_t /*index*/) noexcept {
if (auto const &border = parent.try_as<winrt::Windows::UI::Xaml::Controls::Border>()) {
border.Child(nullptr);
}
}
void CircleMaskViewManager::ReplaceChild(
winrt::Windows::UI::Xaml::FrameworkElement const &parent,
winrt::Windows::UI::Xaml::UIElement const & /*oldChild*/,
winrt::Windows::UI::Xaml::UIElement const &newChild) noexcept {
if (auto const &border = parent.try_as<winrt::Windows::UI::Xaml::Controls::Border>()) {
border.Child(newChild);
}
}
winrt::Windows::Foundation::IInspectable HeightToCornerRadiusConverter::Convert(
winrt::Windows::Foundation::IInspectable const &value,
winrt::Windows::UI::Xaml::Interop::TypeName const & /*targetType*/,
winrt::Windows::Foundation::IInspectable const & /*parameter*/,
winrt::hstring const & /*language*/) noexcept {
double d = winrt::unbox_value<double>(value);
if (isnan(d)) {
d = 0.0;
}
return winrt::box_value(winrt::Windows::UI::Xaml::CornerRadiusHelper::FromUniformRadius(d));
}
winrt::Windows::Foundation::IInspectable HeightToCornerRadiusConverter::ConvertBack(
winrt::Windows::Foundation::IInspectable const &value,
winrt::Windows::UI::Xaml::Interop::TypeName const & /*targetType*/,
winrt::Windows::Foundation::IInspectable const & /*parameter*/,
winrt::hstring const & /*language*/) noexcept {
return value;
}
winrt::Windows::UI::Xaml::Data::IValueConverter HeightToCornerRadiusConverter::Instance() noexcept {
static auto const &instance = winrt::make<HeightToCornerRadiusConverter>();
return instance;
};
#endif // #ifndef RNW_NEW_ARCH
} // namespace winrt::testlib::implementation
如您所见,CircleMask.h
文件定义了三件事
- 一个
RegisterCircleMaskNativeComponent
函数,用于将CircleMask
Paper View Manager 注册到 React Native - 一个包含
CircleMask
Paper View Manager 的CircleMaskViewManager
结构体 - 一个带有某些辅助功能的
HeightToCornerRadiusConverter
结构体
然后在 CircleMask.cpp
中,我们有新原生组件的实现细节。请注意使用 #ifndef RNW_NEW_ARCH
来确保仅当库被旧架构应用程序使用时才包含 Paper View Manager 代码。
注意: 有关如何同时为 Fabric 和 Paper 实现
CircleMask
组件的更完整示例,请参阅 原生模块示例 项目中的实现。
3.2 将原生组件文件添加到原生项目
由于我们创建了一些新的原生文件(上面的 CircleMask.h
和 CircleMask.cpp
),我们需要确保它们包含在原生 Windows 项目中(在我们的示例中是 windows\testlib\testlib.vcxproj
和 windows\testlib\testlib.vcxproj.filters
),以便它们包含在原生构建中
<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>
3.3 将 Paper View Manager 注册到 React 包提供者
每个 React Native for Windows 库都包含一个 IReactPackageProvider
,它包含库的所有原生模块(和/或组件),以便 React Native 可以在运行时使用它们。我们需要的最后一点原生工作是更新 windows\testlib\ReactPackageProvider.cpp
中的 ReactPackageProvider::CreatePackage
#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
这里的关键是添加 #include "CircleMask.h"
头文件并添加对我们之前创建的 RegisterCircleMaskNativeComponent
函数的调用。这确保了新的原生组件包含在库的包中。
4. 在 JavaScript 中使用原生组件
现在,如果我们回到 CircleMaskNativeComponent.ts
TypeScript 规范文件,我们将看到它导出了 CircleMaskProps
接口以及原生组件。下一步是在我们的 JavaScript 代码中使用这些导出的项目。
由于库的目的是将原生功能公开给库外部的代码(即我们的适用于 Windows 的 React Native 应用代码),因此默认是在项目的索引中导出该功能,在本例中为 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';
我们可以看到,testlib
JavaScript 模块只是将我们的新原生组件导出为 CircleMask
,并按原样从 CircleMaskNativeComponent
模块导出所有其他内容。
注意: 库不需要将其任何原生组件直接暴露给其消费者。本示例仅说明了按原样导出
CircleMask
原生组件的最简单情况。库可以而且通常会将其原生组件包装在 JavaScript 组件中,因此可能会向其客户提供完全不同的 API 表面。
后续步骤
实现原生库后,最后一步是在适用于 Windows 的 React Native 应用中使用它。继续阅读 原生平台:使用原生库。