原生 UI 组件
仅限旧架构:本文档描述的功能仅受 React Native 的“旧”或“传统”架构支持。我们仍在更新所有文档,但在此期间,有关 React Native Windows 中 React Native 架构的信息,请参阅新旧架构。
本文档和底层平台代码仍在开发中。 示例(C# 和 C++/WinRT):
有大量的原生 UI 小部件可供最新应用程序使用——其中一些是平台的一部分,另一些作为第三方库提供,还有一些可能正在您自己的作品中使用。React Native 已经封装了一些最关键的平台组件,例如 ScrollView
和 TextInput
,但不是全部,当然也不是您可能为以前的应用程序自己编写的组件。幸运的是,我们可以封装这些现有组件,以便与您的 React Native 应用程序无缝集成。
与原生模块指南类似,这也是一个更高级的指南,假设您对 UWP 编程有所了解。本指南将向您展示如何构建一个原生 UI 组件,通过实现核心 React Native 库中现有 ImageView
组件的子集来引导您完成整个过程。
概述
与编写原生模块类似,在高级别上您必须
- 编写一个视图管理器,它定义了一个新的组件类型,并了解如何创建原生 UI 小部件以及如何与其交互。
- 在 React Native 主应用程序的原生代码中注册您的新视图管理器。
- 在您的 React Native JSX 代码中引用新组件。
关于 UWP XAML 控件的注意事项
一些 UWP XAML 控件不支持在涉及 3D 转换的环境中托管(即,在控件或 XAML 树中控件的任何祖先上设置了 Transform3D
属性)。
目前,React Native for Windows 使用全局 PerspectiveTransform
来为沿 x
或 y
轴旋转的对象提供 3D 外观,这意味着这些在 3D 环境中不工作的控件将无法开箱即用(例如 InkCanvas
)。但是,React Native for Windows 应用程序可以通过在 ReactRootView
上设置 IsPerspectiveEnabled
属性来选择禁用 3D 透视(从而启用使用这些控件)。
重要:IsPerspectiveEnabled
属性是实验性的,未来可能会移除对其的支持。
初始设置
先决条件:遵循原生模块设置指南,创建 Visual Studio 基础设施,以便为 React Native Windows 编写自己的独立原生模块
设置好开发环境和项目结构后,就可以编写代码了。
如果您只打算将原生模块添加到现有的 React Native Windows 应用程序中,即:
- 您遵循了入门指南,其中
- 您使用了init-windows 命令将 Windows 添加到您的项目中,并且
- 您只是将原生代码添加到
windows
文件夹下的应用程序项目中。
那么您只需打开 windows
文件夹中的 Visual Studio 解决方案,并将新文件直接添加到应用程序项目中。
如果您正在创建独立的原生模块,或者为现有原生模块添加 Windows 支持,请首先查看原生模块设置指南。
视图管理器示例
属性
属性 | 使用 |
---|---|
ViewManagerExportedViewConstant | 指定表示常量的字段或属性。 |
ViewManagerProperty | 指定一个方法,用于设置原生 UI 小部件实例上的属性。 |
ViewManagerCommand | 指定可以在原生 UI 小部件实例上调用的方法。 |
对于此示例,假设我们有以下 CustomUserControl
,我们想在 React Native 中使用它。
CustomUserControl.cs
:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ViewManagerSample
{
public sealed class CustomUserControl : Control
{
public static DependencyProperty LabelProperty { get; private set; }
public string Label
{
get
{
return (string)GetValue(LabelProperty);
}
set
{
SetValue(LabelProperty, value);
}
}
static CustomUserControl()
{
LabelProperty = DependencyProperty.Register(
nameof(Label),
typeof(string),
typeof(CustomUserControl),
new PropertyMetadata(default(string))
);
}
public CustomUserControl()
{
DefaultStyleKey = typeof(CustomUserControl);
}
}
}
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ViewManagerSample">
<Style TargetType="local:CustomUserControl" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomUserControl">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Foreground="{TemplateBinding Foreground}" Text="{TemplateBinding Label}" TextAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
1. 编写您的视图管理器
这是一个用 C# 编写的视图管理器示例,名为 CustomUserControlViewManager
。
CustomUserControlViewManager.cs
:
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Controls;
using Microsoft.ReactNative.Managed;
using System.Collections.Generic;
namespace ViewManagerSample
{
internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl>
{
[ViewManagerProperty("label")]
public void SetLabel(CustomUserControl view, string value)
{
if (null != value)
{
view.Label = value;
}
else
{
view.ClearValue(CustomUserControl.LabelProperty);
}
}
[ViewManagerProperty("color")]
public void SetColor(CustomUserControl view, Brush value)
{
if (null != value)
{
view.Foreground = value;
}
else
{
view.ClearValue(Control.ForegroundProperty);
}
}
[ViewManagerProperty("backgroundColor")]
public void SetBackgroundColor(CustomUserControl view, Brush value)
{
if (null != value)
{
view.Background = value;
}
else
{
view.ClearValue(Control.BackgroundProperty);
}
}
[ViewManagerCommand]
public void CustomCommand(CustomUserControl view, IReadOnlyList<object> commandArgs)
{
// Execute command
}
}
}
2. 注册您的视图管理器
与原生模块一样,我们希望向 React Native 注册我们新的 CustomUserControlViewManager
,以便我们能够实际使用它。为此,我们首先创建一个实现 Microsoft.ReactNative.IReactPackageProvider
的 ReactPackageProvider
。
ReactPackageProvider.cs
:
using Microsoft.ReactNative.Managed;
namespace ViewManagerSample
{
public partial class ReactPackageProvider : IReactPackageProvider
{
public void CreatePackage(IReactPackageBuilder packageBuilder)
{
CreatePackageImplementation(packageBuilder);
}
/// <summary>
/// This method is implemented by the C# code generator
/// </summary>
partial void CreatePackageImplementation(IReactPackageBuilder packageBuilder);
}
}
在这里,我们实现了 CreatePackage
方法,该方法接收 packageBuilder
来构建包的内容。
现在我们有了 ReactPackageProvider
,是时候在我们的 ReactApplication
中注册它了。我们只需将提供程序添加到 PackageProviders
属性即可。
App.xaml.cs
:
using Microsoft.ReactNative;
namespace SampleApp
{
sealed partial class App : ReactApplication
{
public App()
{
/* Other Init Code */
PackageProviders.Add(new Microsoft.ReactNative.Managed.ReactPackageProvider()); // Includes any modules in this project
PackageProviders.Add(new ViewManagerSample.ReactPackageProvider());
/* Other Init Code */
}
}
}
此示例假定我们上面创建的 ViewManagerSample.ReactPackageProvider
位于与我们的应用程序不同的项目(程序集)中。但是您会注意到,默认情况下我们也添加了 Microsoft.ReactNative.Managed.ReactPackageProvider
。
Microsoft.ReactNative.Managed.ReactPackageProvider
是一种便利,它确保在应用程序项目中定义的所有原生模块和视图管理器都会自动注册。因此,如果您直接在应用程序项目中创建视图管理器,您实际上不需要定义单独的 ReactPackageProvider
。
更多扩展点
- 在某些情况下,视图管理器在视图创建时可能需要更多上下文,以便决定实例化哪种控件。这可以通过让视图管理器实现
IViewManagerCreateWithProperties
接口来实现。然后,CreateViewWithProperties
方法可以通过检查propertyMapReader
来访问 JSX 中设置的属性。
-internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl> {
+internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl>, IViewManagerCreateWithProperties {
// rest of the view manager goes here...
+ // IViewManagerCreateWithProperties
+ public virtual object CreateViewWithProperties(Microsoft.ReactNative.IJSValueReader propertyMapReader) {
+ propertyMapReader.ReaderValue(out IDictionary<string, JSValue> propertyMap);
+ // create a XAML FrameworkElement based on properties in propertyMap
+ if (propertyMap.ContainsKey("foo)) {
+ return new Button();
+ } else {
+ return new TextBox();
+ }
+ }
}
+}
- 您的视图管理器还能够声明它希望负责自己的大小和布局。这在您封装原生 XAML 控件的场景中很有用。为此,请实现
Microsoft.ReactNative.IViewManagerRequiresNativeLayout
接口
-internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl> {
+internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl>, IViewManagerRequiresNativeLayout {
// rest of the view manager goes here...
+ // IViewManagerRequiresNativeLayout
+ virtual bool RequiresNativeLayout() { return true; }
对于此示例,假设我们已经在 C# 示例中定义了 CustomUserControl
。
1. 编写您的视图管理器
这是一个用 C++ 编写的视图管理器示例,名为 CustomUserControlViewManager
。
CustomUserControlViewManager.h
:
#pragma once
#include "pch.h"
#include "winrt/Microsoft.ReactNative.h"
namespace winrt::ViewManagerSample::implementation {
struct CustomUserControlViewManager : winrt::implements<
CustomUserControlViewManager,
winrt::Microsoft::ReactNative::IViewManager,
winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties,
winrt::Microsoft::ReactNative::IViewManagerWithCommands,
winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants,
winrt::Microsoft::ReactNative::IViewManagerWithReactContext> {
public:
CustomUserControlViewManager() = default;
// IViewManager
winrt::hstring Name() noexcept;
winrt::Windows::UI::Xaml::FrameworkElement CreateView() noexcept;
// IViewManagerWithNativeProperties
winrt::Windows::Foundation::Collections::
IMapView<winrt::hstring, winrt::Microsoft::ReactNative::ViewManagerPropertyType>
NativeProps() noexcept;
void UpdateProperties(
winrt::Windows::UI::Xaml::FrameworkElement const &view,
winrt::Microsoft::ReactNative::IJSValueReader const &propertyMapReader) noexcept;
// IViewManagerWithCommands
winrt::Windows::Foundation::Collections::IVectorView<winrt::hstring> Commands() noexcept;
void DispatchCommand(
winrt::Windows::UI::Xaml::FrameworkElement const &view,
winrt::hstring const &commandId,
winrt::Microsoft::ReactNative::IJSValueReader const &commandArgsReader) noexcept;
// IViewManagerWithExportedEventTypeConstants
winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomBubblingEventTypeConstants() noexcept;
winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomDirectEventTypeConstants() noexcept;
// IViewManagerWithReactContext
winrt::Microsoft::ReactNative::IReactContext ReactContext() noexcept;
void ReactContext(winrt::Microsoft::ReactNative::IReactContext reactContext) noexcept;
private:
winrt::Microsoft::ReactNative::IReactContext m_reactContext{ nullptr };
};
}
CustomUserControlViewManager.cpp
:
#include "pch.h"
#include "CustomUserControlViewManager.h"
#include "JSValueReader.h"
#include "JSValueXaml.h"
#include "NativeModules.h"
using namespace winrt;
using namespace Microsoft::ReactNative;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Controls;
namespace winrt::ViewManagerSample::implementation {
// IViewManager
hstring CustomUserControlViewManager::Name() noexcept {
return L"CustomUserControl";
}
FrameworkElement CustomUserControlViewManager::CreateView() noexcept {
return winrt::ViewManagerSample::CustomUserControl();
}
// IViewManagerWithNativeProperties
IMapView<hstring, ViewManagerPropertyType> CustomUserControlViewManager::NativeProps() noexcept {
auto nativeProps = winrt::single_threaded_map<hstring, ViewManagerPropertyType>();
nativeProps.Insert(L"label", ViewManagerPropertyType::String);
nativeProps.Insert(L"color", ViewManagerPropertyType::Color);
nativeProps.Insert(L"backgroundColor", ViewManagerPropertyType::Color);
return nativeProps.GetView();
}
void CustomUserControlViewManager::UpdateProperties(
FrameworkElement const &view,
IJSValueReader const &propertyMapReader) noexcept {
if (auto control = view.try_as<winrt::ViewManagerSample::CustomUserControl>()) {
const JSValueObject &propertyMap = JSValue::ReadObjectFrom(propertyMapReader);
for (auto const &pair : propertyMap) {
auto const &propertyName = pair.first;
auto const &propertyValue = pair.second;
if (propertyName == "label") {
if (propertyValue != nullptr) {
auto const &value = winrt::box_value(winrt::to_hstring(propertyValue.String()));
control.Label(value);
} else {
control.ClearValue(winrt::ViewManagerSample::CustomUserControl::LabelProperty());
}
} else if (propertyName == "color") {
if (auto value = propertyValue.To<Brush>()) {
control.Foreground(value);
} else {
control.ClearValue(Control::ForegroundProperty());
}
} else if (propertyName == "backgroundColor") {
if (auto value = propertyValue.To<Brush>()) {
control.Background(value);
} else {
control.ClearValue(Control::BackgroundProperty());
}
}
}
}
}
// IViewManagerWithCommands
IVectorView<hstring> CustomUserControlViewManager::Commands() noexcept {
auto commands = winrt::single_threaded_vector<hstring>();
commands.Append(L"CustomCommand");
return commands.GetView();
}
void CustomUserControlViewManager::DispatchCommand(
FrameworkElement const &view,
winrt::hstring const &commandId,
winrt::Microsoft::ReactNative::IJSValueReader const &commandArgsReader) noexcept {
if (auto control = view.try_as<winrt::ViewManagerSample::CustomUserControl>()) {
if (commandId == L"CustomCommand") {
const JSValueArray &commandArgs = JSValue::ReadArrayFrom(commandArgsReader);
// Execute command
}
}
}
// IViewManagerWithExportedEventTypeConstants
ConstantProviderDelegate CustomUserControlViewManager::ExportedCustomBubblingEventTypeConstants() noexcept {
return [](winrt::Microsoft::ReactNative::IJSValueWriter const& constantWriter) {
// use constantWriter to define bubbling events, see ExportedCustomDirectEventTypeConstants
}
}
ConstantProviderDelegate CustomUserControlViewManager::ExportedCustomDirectEventTypeConstants() noexcept {
return [](winrt::Microsoft::ReactNative::IJSValueWriter const& constantWriter) {
constantWriter.WritePropertyName(L"topMyEvent");
constantWriter.WriteObjectBegin();
WriteProperty(constantWriter, L"registrationName", L"onMyEvent");
constantWriter.WriteObjectEnd();
};
}
// IViewManagerWithReactContext
IReactContext CustomUserControlViewManager::ReactContext() noexcept {
return m_reactContext;
}
void CustomUserControlViewManager::ReactContext(IReactContext reactContext) noexcept {
m_reactContext = reactContext;
}
}
更多扩展点
- 在某些情况下,视图管理器在视图创建时可能需要更多上下文,以便决定实例化哪种控件。这可以通过让视图管理器实现
IViewManagerCreateWithProperties
接口来实现
struct CustomUserControlViewManager : winrt::implements<
CustomUserControlViewManager,
winrt::Microsoft::ReactNative::IViewManager,
winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties,
winrt::Microsoft::ReactNative::IViewManagerWithCommands,
winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants,
+ winrt::Microsoft::ReactNative::IViewManagerCreateWithProperties,
winrt::Microsoft::ReactNative::IViewManagerWithReactContext> {
+ // IViewManagerCreateWithProperties
+ winrt::Windows::Foundation::IInspectable CreateViewWithProperties(winrt::Microsoft::ReactNative::IJSValueReader const &propertyMapReader);
然后,CreateViewWithProperties
方法可以通过检查 propertyMapReader
来访问 JSX 中设置的属性,就像在 UpdateProperties
方法中完成的那样。
- 您的视图管理器还能够声明它希望负责自己的大小和布局。这在您封装原生 XAML 控件的场景中很有用。为此,请实现
winrt::Microsoft::ReactNative::IViewManagerRequiresNativeLayout
接口
struct CustomUserControlViewManager : winrt::implements<
CustomUserControlViewManager,
winrt::Microsoft::ReactNative::IViewManager,
winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties,
winrt::Microsoft::ReactNative::IViewManagerWithCommands,
winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants,
+ winrt::Microsoft::ReactNative::IViewManagerRequiresNativeLayout,
winrt::Microsoft::ReactNative::IViewManagerWithReactContext> {
+ // IViewManagerRequiresNativeLayout
+ bool RequiresNativeLayout() { return true; }
2. 注册您的视图管理器
与原生模块一样,我们希望向 React Native 注册我们新的 CustomUserControlViewManager
,以便我们能够实际使用它。为此,我们首先创建一个实现 Microsoft.ReactNative.IReactPackageProvider
的 ReactPackageProvider
。
ReactPackageProvider.idl
:
namespace ViewManagerSample
{
[webhosthidden]
[default_interface]
runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider
{
ReactPackageProvider();
};
}
之后我们添加 .h 和 .cpp 文件
ReactPackageProvider.h
:
#pragma once
#include "ReactPackageProvider.g.h"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::ViewManagerSample::implementation
{
struct ReactPackageProvider : ReactPackageProviderT<ReactPackageProvider>
{
ReactPackageProvider() = default;
void CreatePackage(IReactPackageBuilder const& packageBuilder) noexcept;
};
}
namespace winrt::ViewManagerSample::factory_implementation
{
struct ReactPackageProvider : ReactPackageProviderT<ReactPackageProvider, implementation::ReactPackageProvider> {};
}
ReactPackageProvider.cpp
:
#include "pch.h"
#include "ReactPackageProvider.h"
#include "ReactPackageProvider.g.cpp"
#include <ModuleRegistration.h>
// NOTE: You must include the headers of your native modules here in
// order for the AddAttributedModules call below to find them.
#include "CustomUserControlViewManager.h"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::ViewManagerSample::implementation {
void ReactPackageProvider::CreatePackage(IReactPackageBuilder const& packageBuilder)
noexcept {
packageBuilder.AddViewManager(
L"CustomUserControlViewManager", []() { return winrt::make<CustomUserControlViewManager>(); });
}
} // namespace winrt::ViewManagerSample::implementation
在这里,我们实现了 CreatePackage
方法,该方法接收 packageBuilder
来构建包的内容。然后我们调用 AddViewManager
,传入我们的视图管理器的名称和一个返回视图管理器实例的 lambda 表达式。
现在我们有了 ReactPackageProvider
,是时候在我们的 ReactApplication
中注册它了。我们只需将提供程序添加到 PackageProviders
属性即可。
App.cpp
:
#include "pch.h"
#include "App.h"
#include "ReactPackageProvider.h"
#include "winrt/ViewManagerSample.h"
namespace winrt::SampleApp::implementation {
App::App() noexcept {
/* Other Init Code */
PackageProviders().Append(make<ReactPackageProvider>()); // Includes all modules in this project
PackageProviders().Append(winrt::ViewManagerSample::ReactPackageProvider());
/* Other Init Code */
}
} // namespace winrt::SampleApp::implementation
此示例假定我们上面创建的 ViewManagerSample::ReactPackageProvider
位于与我们的应用程序不同的项目(程序集)中。但是您会注意到,默认情况下我们也添加了 SampleApp::ReactPackageProvider
。
SampleApp::ReactPackageProvider
是一种便利,它确保在应用程序项目中定义的所有原生模块和视图管理器都会自动注册。因此,如果您直接在应用程序项目中创建原生模块,您实际上不需要定义单独的 ReactPackageProvider
。
3. 在 JSX 中使用您的视图管理器
ViewManagerSample.js
:
import React, { Component } from 'react';
import {
AppRegistry,
Button,
requireNativeComponent,
StyleSheet,
UIManager,
View,
} from 'react-native';
let CustomUserControl = requireNativeComponent('CustomUserControl');
class ViewManagerSample extends Component {
onPress() {
if (_customControlRef) {
const tag = findNodeHandle(this._customControlRef);
UIManager.dispatchViewManagerCommand(tag,
UIManager.getViewManagerConfig('CustomUserControl').Commands.CustomCommand,
['arg1', 'arg2']);
}
}
render() {
return (
<View style={styles.container}>
<CustomUserControl style={styles.customcontrol}
label="CustomUserControl!"
ref={(ref) => { this._customControlRef = ref; }} />
<Button onPress={() => { this.onPress(); }}
title="Call CustomUserControl Commands!" />
</View>);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
customcontrol: {
color: '#333333',
backgroundColor: '#006666',
width: 200,
height: 20,
margin: 10,
},
});
AppRegistry.registerComponent('ViewManagerSample', () => ViewManagerSample);