Skip to main content

Fabric 组件作为传统原生组件


提醒

本文档仍为 experimental,详细信息可能会随着我们的迭代而发生变化。 欢迎在此页面分享你对 工作组内部讨论 的反馈。

This documentation is still experimental and details are subject to changes as we iterate. Feel free to share your feedback on the discussion inside the working group for this page.

而且,它还包含几个 手动步骤。 请注意,一旦新架构稳定,这将不代表最终的开发者体验。 我们正在开发工具、模板和库,以帮助你快速开始使用新架构,而无需完成整个设置。

Moreover, it contains several manual steps. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup.

信息

创建向后兼容的 Fabric 原生组件需要了解如何创建旧原生组件。 要回忆这些概念,请查看此 guide

Creating a backward compatible Fabric Native Component requires the knowledge of how to create a Legacy Native Component. To recall these concepts, have a look at this guide.

Fabric Native 组件仅在新架构正确设置后才能工作。 如果你已经有一个库想要迁移到新架构,也可以看看 迁移指南

创建向后兼容的 Fabric Native 组件可以让你的用户继续独立于他们使用的架构来利用你的库。 创建这样的组件需要几个步骤:

Creating a backward compatible Fabric Native Component lets your users continue to leverage your library independently from the architecture they use. The creation of such a component requires a few steps:

  1. 配置库,以便准备好为旧架构和新架构正确设置依赖。
  2. 更新代码库,以便新架构类型在不可用时不会被编译。
  3. 统一 JavaScript API,这样你的用户代码就不需要更改。
信息

为了本指南的目的,我们将使用以下 terminology

For the sake of this guide we're going to use the following terminology:

  • 旧版的原生组件 - 指在旧的 React Native 架构上运行的组件。
  • Fabric 原生组件 - 指的是经过改编以与新的原生渲染器 Fabric 配合良好的组件。 为了简洁起见,你可能会发现它们被称为 Fabric 组件
提醒

对新架构的 TypeScript 支持仍处于测试阶段。

虽然最后一步对于所有平台都是相同的,但前两个步骤对于 iOS 和 Android 来说是不同的。

While the last step is the same for all the platforms, the first two steps are different for iOS and Android.

配置 Fabric 原生组件依赖

iOS

Apple 平台使用 CocoaPods 作为依赖管理器来安装 Fabric Native Components。

The Apple platform installs Fabric Native Components using CocoaPods as a dependency manager.

如果你已经使用 install_module_dependencies 功能,则使用 没有什么可以做的。 该函数已经负责在启用新架构时安装适当的依赖,并在未启用时避免安装它们。

If you are already using the install_module_dependencies function, then there is nothing to do. The function already takes care of installing the proper dependencies when the New Architecture is enabled and avoiding them when it is not enabled.

否则,你的 Fabric Native 组件的 podspec 应如下所示:

Otherwise, your Fabric Native Component's podspec should look like this:

require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'

Pod::Spec.new do |s|
# Default fields for a valid podspec
s.name = "<FC Name>"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "11.0" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,swift}"
# React Native Core dependency
s.dependency "React-Core"

# The following lines are required by the New Architecture.
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}

s.dependency "React-RCTFabric"
s.dependency "React-Codegen"
s.dependency "RCT-Folly"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
end

你应该在启用新架构时安装额外的依赖,并在未启用时避免安装它们。 为此,你可以使用 install_modules_dependencies。 更新 .podspec 文件,如下所示:

You should install the extra dependencies when the New Architecture is enabled, and avoid installing them when it's not. To achieve this, you can use the install_modules_dependencies. Update the .podspec file as it follows:

require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

- folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'

Pod::Spec.new do |s|
# Default fields for a valid podspec
s.name = "<FC Name>"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "11.0" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,swift}"
# React Native Core dependency
+ install_modules_dependencies(s)
- s.dependency "React-Core"
- # The following lines are required by the New Architecture.
- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
- s.pod_target_xcconfig = {
- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
- "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
- }
-
- s.dependency "React-RCTFabric"
- s.dependency "React-Codegen"
- s.dependency "RCT-Folly"
- s.dependency "RCTRequired"
- s.dependency "RCTTypeSafety"
- s.dependency "ReactCommon/turbomodule/core"
end

安卓

要创建可以与两种架构一起使用的原生组件,你需要配置 Gradle 以根据所选架构选择需要编译的文件。 这可以通过在 Gradle 配置中使用 不同的源集 来实现。

To create a Native Component that can work with both architectures, you need to configure Gradle to choose which files need to be compiled depending on the chosen architecture. This can be achieved by using different source sets in the Gradle configuration.

注意

请注意,这是目前建议的方法。 虽然这可能会导致一些代码重复,但它将确保与两种架构的最大兼容性。 你将在下一节中看到如何减少重复。

要配置 Fabric Native 组件以便它选择正确的源集,你必须按以下方式更新 build.gradle 文件:

To configure the Fabric Native Component so that it picks the proper sourceset, you have to update the build.gradle file in the following way:

build.gradle
+// Add this function in case you don't have it already
+ def isNewArchitectureEnabled() {
+ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
+}
// ... other parts of the build file
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 21)
targetSdkVersion safeExtGet('targetSdkVersion', 31)
+ buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString())
+ }
+
+ sourceSets {
+ main {
+ if (isNewArchitectureEnabled()) {
+ java.srcDirs += ['src/newarch']
+ } else {
+ java.srcDirs += ['src/oldarch']
+ }
+ }
}
}

这些变化主要做了三件事:

These changes do three main things:

  1. 第一行定义了一个函数,该函数返回新架构是否启用。
  2. buildConfigField 行定义了一个名为 IS_NEW_ARCHITECTURE_ENABLED 的构建配置布尔字段,并使用第一步中声明的函数对其进行初始化。 这允许你在运行时检查用户是否指定了 newArchEnabled 属性。
  3. 最后几行利用第一步中声明的函数来决定我们需要构建哪些源集,具体取决于所选的体系结构。

更新代码库

iOS

第二步是指示 Xcode 在我们使用旧架构构建应用时避免使用新架构类型和文件编译所有行。

The second step is to instruct Xcode to avoid compiling all the lines using the New Architecture types and files when we are building an app with the Old Architecture.

Fabric Native 组件需要一个头文件和一个实现文件来将实际的 View 添加到模块中。

A Fabric Native Component requires a header file and an implementation file to add the actual View to the module.

例如,RNMyComponentView.h 头文件可能如下所示:

For example, the RNMyComponentView.h header file could look like this:

RNMyComponentView.h
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>

#ifndef NativeComponentExampleComponentView_h
#define NativeComponentExampleComponentView_h

NS_ASSUME_NONNULL_BEGIN

@interface RNMyComponentView : RCTViewComponentView
@end

NS_ASSUME_NONNULL_END

#endif /* NativeComponentExampleComponentView_h */

相反,实现 RNMyComponentView.mm 文件可能如下所示:

The implementation RNMyComponentView.mm file, instead, could look like this:

RNMyComponentView.mm
#import "RNMyComponentView.h"

// <react/renderer imports>

#import "RCTFabricComponentsPlugins.h"

using namespace facebook::react;

@interface RNMyComponentView () <RCTMyComponentViewViewProtocol>

@end

@implementation RNMyComponentView {
UIView * _view;
}

+ (ComponentDescriptorProvider)componentDescriptorProvider
{
// ... return the descriptor ...
}

- (instancetype)initWithFrame:(CGRect)frame
{
// ... initialize the object ...
}

- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
// ... set up the props ...

[super updateProps:props oldProps:oldProps];
}

Class<RCTComponentViewProtocol> MyComponentViewCls(void)
{
return RNMyComponentView.class;
}

@end

为了确保 Xcode 跳过这些文件,我们可以将其中的 both 封装在一些 #ifdef RCT_NEW_ARCH_ENABLED 编译指示中。 例如,头文件可以更改如下:

To make sure that Xcode skips these files, we can wrap both of them in some #ifdef RCT_NEW_ARCH_ENABLED compilation pragma. For example, the header file could change as follows:

+ #ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>

// ... rest of the header file ...

#endif /* NativeComponentExampleComponentView_h */
+ #endif

应在实现文件中添加相同的两行,作为第一行和最后一行。

The same two lines should be added in the implementation file, as first and last lines.

上面的代码片段使用了前面 section 中使用的相同 RCT_NEW_ARCH_ENABLED 标志。 如果未设置此标志,Xcode 在编译期间会跳过 #ifdef 中的行,并且不会将它们包含到编译的二进制文件中。 编译后的二进制文件将有一个 RNMyComponentView.o 对象,但它将是一个空对象。

The above snippet uses the same RCT_NEW_ARCH_ENABLED flag used in the previous section. When this flag is not set, Xcode skips the lines within the #ifdef during compilation and it does not include them into the compiled binary. The compiled binary will have a the RNMyComponentView.o object but it will be an empty object.

使用 #ifdef 编译指示封装上述组件后,你需要按照 旧版原生组件文档 实现遗留架构的组件。 这是必需的,因为新渲染器的工作方式与旧渲染器不同,并且它无法遵循新渲染器的新代码路径。

After wrapping the above components with a #ifdef pragma, you need to implement the component for the legacy architecture, following the legacy Native Component documentation. This is needed because the New Renderer works in a different way from the legacy one, and it is not able to follow the new code's paths of the New Renderer.

安卓

由于我们无法在 Android 上使用条件编译块,因此我们将定义两个不同的源集。 这将允许创建一个向后兼容的 TurboModule,并根据所使用的架构加载和编译适当的源代码。

As we can't use conditional compilation blocks on Android, we will define two different source sets. This will allow to create a backward compatible TurboModule with the proper source that is loaded and compiled depending on the used architecture.

因此,你必须:

Therefore, you have to:

  1. src/oldarch 路径中创建旧原生组件。 请参阅 本指南 了解如何创建旧原生组件。
  2. src/newarch 路径中创建 Fabric Native 组件。 请参阅 本指南 了解如何创建 Fabric 原生组件。

然后指示 Gradle 决定选择哪个实现。

and then instruct Gradle to decide which implementation to pick.

一些文件可以在旧组件和 Fabric 组件之间共享: 这些应该被创建或移动到由两种架构加载的文件夹中。 这些文件是:

Some files can be shared between a Legacy and a Fabric Component: these should be created or moved into a folder that is loaded by both the architectures. These files are:

  • <MyComponentView>.java 为两个组件实例化和配置 Android 视图。
  • <MyComponentView>ManagerImpl.java 文件,其中包含可以在旧组件和 Fabric 组件之间共享的 ViewManager 逻辑。
  • 用于加载组件的 <MyComponentView>Package.java 文件。

最终的文件夹结构如下所示:

The final folder structure looks like this:

my-component
├── android
│ ├── build.gradle
│ └── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── mycomponent
│ │ ├── MyComponentView.java
│ │ ├── MyComponentViewManagerImpl.java
│ │ └── MyComponentViewPackage.java
│ ├── newarch
│ │ └── java
│ │ └── com
│ │ └── MyComponentViewManager.java
│ └── oldarch
│ └── java
│ └── com
│ └── MyComponentViewManager.java
├── ios
├── js
└── package.json

应该放入 MyComponentViewManagerImpl.java 并可以在原生组件和 Fabric 原生组件之间共享的代码例如是:

The code that should go in the MyComponentViewManagerImpl.java and that can be shared between the Native Component and the Fabric Native Component is, for example:

example of MyComponentViewManager.java
package com.mycomponent;

import androidx.annotation.Nullable;
import com.facebook.react.uimanager.ThemedReactContext;

public class MyComponentViewManagerImpl {

public static final String NAME = "MyComponent";

public static MyComponentView createViewInstance(ThemedReactContext context) {
return new MyComponentView(context);
}

public static void setFoo(MyComponentView view, String param) {
// implement the logic of the foo function using the view and the param passed.
}
}

然后,可以使用共享管理器中声明的函数来更新原生组件和 Fabric 原生组件。

Then, the Native Component and the Fabric Native Component can be updated using the function declared in the shared manager.

例如,对于原生组件:

For example, for a Native Component:

Native Component using the ViewManagerImpl
public class MyComponentViewManager extends SimpleViewManager<MyComponentView> {

ReactApplicationContext mCallerContext;

public MyComponentViewManager(ReactApplicationContext reactContext) {
mCallerContext = reactContext;
}

@Override
public String getName() {
// static NAME property from the shared implementation
return MyComponentViewManagerImpl.NAME;
}

@Override
public MyComponentView createViewInstance(ThemedReactContext context) {
// static createViewInstance function from the shared implementation
return MyComponentViewManagerImpl.createViewInstance(context);
}

@ReactProp(name = "foo")
public void setFoo(MyComponentView view, String param) {
// static custom function from the shared implementation
MyComponentViewManagerImpl.setFoo(view, param);
}

}

并且,对于 Fabric 原生组件:

And, for a Fabric Native Component:

Fabric Component using the ViewManagerImpl
// Use the static NAME property from the shared implementation
@ReactModule(name = MyComponentViewManagerImpl.NAME)
public class MyComponentViewManager extends SimpleViewManager<MyComponentView>
implements MyComponentViewManagerInterface<MyComponentView> {

private final ViewManagerDelegate<MyComponentView> mDelegate;

public MyComponentViewManager(ReactApplicationContext context) {
mDelegate = new MyComponentViewManagerDelegate<>(this);
}

@Nullable
@Override
protected ViewManagerDelegate<MyComponentView> getDelegate() {
return mDelegate;
}

@NonNull
@Override
public String getName() {
// static NAME property from the shared implementation
return MyComponentViewManagerImpl.NAME;
}

@NonNull
@Override
protected MyComponentView createViewInstance(@NonNull ThemedReactContext context) {
// static createViewInstance function from the shared implementation
return MyComponentViewManagerImpl.createViewInstance(context);
}

@Override
@ReactProp(name = "foo")
public void setFoo(MyComponentView view, @Nullable String param) {
// static custom function from the shared implementation
MyComponentViewManagerImpl.setFoo(view, param);
}
}

有关如何实现此目标的分步示例,请查看 这个仓库

For a step-by-step example on how to achieve this, have a look at this repo.

统一 JavaScript 规范

提醒

对新架构的 TypeScript 支持仍处于测试阶段。

最后一步确保 JavaScript 对所选架构的行为是透明的。

The last step makes sure that the JavaScript behaves transparently to chosen architecture.

对于 Fabric Native 组件,真实来源是 <YourModule>NativeComponent.js(或 .ts)规范文件。 该应用像这样访问规范文件:

For a Fabric Native Component, the source of truth is the <YourModule>NativeComponent.js (or .ts) spec file. The app accesses the spec file like this:

import MyComponent from 'your-component/src/index';

由于 codegenNativeComponent 在后台调用 requireNativeComponent,我们需要重新导出我们的组件,以避免多次注册它。

Since codegenNativeComponent is calling the requireNativeComponent under the hood, we need to re-export our component, to avoid registering it multiple times.

// @flow
export default require('./MyComponentNativeComponent').default;