亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定

React Native新架構:那些棘手的部分(上篇)

终于等到了——随着React Native 0.76版本的发布,新的架构将成为创建应用的默认方式。如果你还没有听说过这个,可以查看这个页面以了解React Native文档中的相关信息。对于用户来说,这种变化应该是透明的,但库的开发者可能需要做一些修改。随着API在过去的几年里逐渐稳定,现在是让库适应新架构的最佳时机。新的架构通过一系列底层的改动,现在可以实现以前无法实现的功能。

在这篇文章中,我们将介绍一些新的概念和可能性,并说明你如何可以利用它们。在继续阅读之前,请先熟悉我们在本文中将用到的术语,并了解渲染管道

平面视图

视图扁平化技术是一种渲染优化,允许跳过创建那些只会对布局产生影响的原生视图。这大大减少了应用程序中创建的原生视图的数量,在使用 React Native 的情况下。决定是否扁平化视图的算法做得非常出色。除非你查看原生视图的层次结构,否则很难看出它实际上是否在起作用。

遗憾的是,它可能会引发一些问题。当 TextInput 组件在原生视图层次结构中被移动时,它们将失去焦点。这就是视图扁平化起作用时发生的情况。让我们考虑以下场景:

  1. 有一个 TextInput 组件被渲染在一个只有内边距样式的 View 中,让它仅仅作为一个布局元素存在。
  2. 点击 TextInput,它就获得了焦点。
  3. 输入了一些文本,但并不是有效的电子邮件地址。
  4. View 的背景变红,表示有错误。
  5. View 不再只是布局元素,还需要在屏幕上显示。
  6. TextInput 被移动到了红色视图之下,从而失去了焦点。

库也可能受到视图扁平化的影响,因为这种情况只影响到渲染管道的最后一步——挂载。React Native Gesture Handler 就是这样的一个库。它提供 原生手势识别器,因此需要将其附加到实际的原生视图上。在这种情况下,首个将被挂载在 GestureDetector 下的组件可能会被扁平化。这时,检测器将尝试将手势附加到一个实际上不存在的原生视图上,导致手势无法工作。幸运的是,Gesture Handler 能够检测并报告这种情况,同时提示你需要为此特定视图禁用扁平化。

你可能想知道如何禁用它。只需在不想折叠的视图上设置一个属性 collapsable

    <View collapsable={false}>  
      {/* 此视图不可折叠 */}
    </View>
查看回收站

视图再利用是新架构引入的另一种优化。当视图被卸载时,React Native 不再销毁它们,而是将它们放入回收池中等待重用,不再在需要时重新创建视图。当内存使用量高时,该池会被清空以防止应用被操作系统终止。默认在 iOS 上启用,而在 Android 上则需要通过[功能标志]启用。

在 iOS 中,每个视图默认都会被回收 — 当它被移除或不再使用时,会调用其 prepareForRecycle 方法,你可以在这里重置任何自定义属性,之后视图会被放入回收池中,直到需要相同类型的视图时再被使用。

    - (void)准备重新使用  
    {  
      [super 准备重新使用];  

      // Reset any custom properties here  
      [_customView setBackgroundColor:[UIColor black]];  
    }

如果你想让你的自定义视图不被回收,你可以在 ComponentView 上添加一个静态的 shouldBeRecycled 方法,使其返回 NO

// 是否应该回收
+ (BOOL)shouldBeRecycled  
{  
    // 返回 不应该回收;
    return NO;  
}

在 Android 上,首先将 enableViewRecycling 功能标志设置为 true 以启用视图回收,然后通过调用你的 ViewManager_setupViewRecycling 方法来回收。为了将任何可能已更改的属性重置为其默认值,你可以实现一个 prepareToRecycleView 方法。

    init {  
      setupViewRecycling()  
    }  

    override fun prepareToRecycleView(reactContext: ThemedReactContext, view: View): View? {  
      // 在这里重置任何自定义属性  
      view.setBackgroundColor(Color.BLACK)  

      return super.prepareToRecycleView(reactContext, view)  
    }

你可以看看this分支以获取完整例子。

支持新版本架构在你的库中

随着新架构变得越来越完善和稳定,相关的文档也变得更加丰富和详细。几乎所有内容都由 Meta 新架构工作组提供的优秀指南涵盖,可在 GitHub 查看。不论是使用 Kotlin 或 Objective-C 进行平台特定开发,还是使用 C++ 实现跨平台开发,这些指南都会教你如何创建 Fabric 组件或 Turbo 模块。如果你想找一个简短的总结,可以查看 这个提交,它介绍了如何迁移视图和模块。还有一些更复杂的话题未在这些指南中详述,但在特定情况下可能非常有用。接下来我们将深入探讨新架构可以实现的高级功能。

自定义阴影结点

自定义阴影节点是一个非常强大的工具,可让您在渲染管道的每个阶段影响组件的行为。当你为 fabric 组件实现自定义阴影节点时,最好的开始方式是复制 codegen 生成且已知可以正常工作的相关代码。记得那里会有一些内容混在一起,但你只需要关注与特定组件相关的部分。

可以在您的应用中找到生成的文件,位于 ios/build/generated/ios/react/renderer/components/<CODEGEN NAME>/android/build/generated/source/codegen/jni/react/renderer/components/<CODEGEN NAME>/<CODEGEN NAME> 指的是在 package.json 文件中的 codegenConfig 字段设置的名称。

你需要创建实际的 ShadowNode 文件及其状态和组件描述符的头文件。你可以从生成的 ShadowNodes.hShadowNodes.cppStates.hComponentDescriptors.h 中复制内容。此时的一个有用改变是将 ShadowNode 及其描述符的定义改为继承具体的类,而不是使用别名——你可能需要自定义其行为。将它们放在 shared 目录下,这个目录会在 Android 和 iOS 上都使用。

    // NewArchTricksViewComponentDescriptor.h  
    #pragma once  // 该指令用于防止头文件被多次包含
    #include <react/debug/react_native_assert.h>  // 引入react_native的断言宏
    #include <react/renderer/components/NewArchTricksSpec/Props.h>  
    #include <react/renderer/core/ConcreteComponentDescriptor.h>  
    #include <react/renderer/components/NewArchTricksSpec/NewArchTricksViewShadowNode.h>  
    namespace facebook {  
    namespace react {  

    // NewArchTricksViewComponentDescriptor类,继承自ConcreteComponentDescriptor<NewArchTricksViewShadowNode>
    class NewArchTricksViewComponentDescriptor final  
        : public ConcreteComponentDescriptor<NewArchTricksViewShadowNode> {  
     public:  
      using ConcreteComponentDescriptor::ConcreteComponentDescriptor;  // 使用基类的构造函数
      // adopt方法用于适配ShadowNode,它会断言传入的ShadowNode是NewArchTricksViewShadowNode类型,并调用基类的adopt方法
      void adopt(ShadowNode &shadowNode) const override {  
        react_native_assert(dynamic_cast<NewArchTricksViewShadowNode *>(&shadowNode));  // 断言shadowNode是NewArchTricksViewShadowNode类型
        ConcreteComponentDescriptor::adopt(shadowNode);  // 调用基类的adopt方法
      }  
    };  

    } // namespace react  
    } // namespace facebook
// NewArchTricksViewShadowNode.cpp 文件中的代码
// 包含 react 渲染器中的 NewArchTricksView 组件的阴影节点实现。
// 下面的命名空间定义了一个外部的字符数组,用于存储组件名 "NewArchTricksView"。

// 保留原代码,不做翻译

namespace facebook::react {  
extern const char NewArchTricksViewComponentName[] = "NewArchTricksView";  
} // namespace facebook::react
    // NewArchTricksViewShadowNode.h  
    #pragma once  
    #include <react/renderer/components/NewArchTricksSpec/EventEmitters.h>  
    #include <react/renderer/components/NewArchTricksSpec/Props.h>  
    #include <react/renderer/components/view/ConcreteViewShadowNode.h>  
    #include <react/renderer/components/NewArchTricksSpec/NewArchTricksViewState.h>  
    #include <jsi/jsi.h>  

    namespace facebook::react {  

    JSI_EXPORT extern const char NewArchTricksViewComponentName[];  
    /*  

* `NewArchTricksViewShadowNode` 对应的 <NewArchTricksView> 组件。  
     */  
    class NewArchTricksViewShadowNode final : public ConcreteViewShadowNode<  
        NewArchTricksViewComponentName,  
        NewArchTricksViewProps,  
        NewArchTricksViewEventEmitter,  
        NewArchTricksViewState> {  
      public:  
      using ConcreteViewShadowNode::ConcreteViewShadowNode;  
    };  

    } // namespace facebook::react
// NewArchTricksViewState.h  
#pragma once  

#ifdef ANDROID  
#include <folly/dynamic.h>  
#endif  

namespace facebook::react {  

class NewArchTricksViewState {  
public:  
  NewArchTricksViewState() = default;  
#ifdef ANDROID  
  NewArchTricksViewState(NewArchTricksViewState const &previousState, folly::dynamic data) {};  
  folly::dynamic getDynamic() const {  
    return {};  
  }  
#endif  
};  

} // } 命名空间 facebook::react

下一步是更新代码生成配置文件,让它不再生成你刚刚复制的部分,这样就可以避免重复的元素。

// 新架构技巧原生组件
// NewArchTricksNativeComponent.ts
export default codegenNativeComponent<NativeProps>('NewArchTricksView', {
  interfaceOnly: true,
});

现在你已经准备好实现的准备工作,可以开始在项目中使用它了。我们将从iOS开始,因为相对简单——你只需要将创建的文件包含到一个子规格中,并在相关的ComponentView文件中更改导入路径——就这样,搞定!

    # react-native-new-arch-tricks.podspec  
    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|  
      s.name         = "react-native-new-arch-tricks"  
      s.version      = package["version"]  
      s.summary      = package["description"]  
      s.homepage     = package["homepage"]  
      s.license      = package["license"]  
      s.authors      = package["author"]  

      s.platforms    = { :ios => min_ios_version_supported }  
      s.source       = { :git => "https://github.com/j-piasecki/react-native-new-arch-tricks.git", :tag => "#{s.version}" }  

      s.source_files = "ios/**/*.{h,m,mm}"  

      install_modules_dependencies(s)  

    +  s.subspec "shared" do |ss|  
    +    ss.source_files         = ["shared/**/*.{cpp,h}"]  
    +    ss.header_dir           = "NewArchTricks"  
    +    ss.pod_target_xcconfig  = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/shared\"" }  
    +  end  
    end
    // NewArchTricksViewComponentView.mm  

    #import "NewArchTricksViewComponentView.h"  

    // 没有这个头文件,RN将无法找到提供者
    #import <React/RCTFabricComponentsPlugins.h>  

    -#import <react/renderer/components/NewArchTricksSpec/ComponentDescriptors.h>  
    +// 导入自定义头文件以便我们可以使用自己的组件描述符
    +#import <NewArchTricks/NewArchTricksViewComponentDescriptor.h>  
    #import <react/renderer/components/NewArchTricksSpec/EventEmitters.h>  
    #import <react/renderer/components/NewArchTricksSpec/Props.h>  
    #import <react/renderer/components/NewArchTricksSpec/RCTComponentViewHelpers.h>

在Android上则更复杂一些——你需要配置React Native,使其在构建时包含你自定义的C++代码。这是通过一个名为react-native.config.js的文件实现的,该文件会指定你定义的组件描述符,并说明如何使用自定义的CMake文件来构建。

/*
react-native.config.js
*/
module.exports = {  
  dependency: {  
    platforms: {  
      android: {  
        componentDescriptors: ['NewArchTricksViewComponentDescriptor'],  
        cmakeListsPath: './CMakeLists.txt',  
      },  
    },  
  },  
};

记住你在配置文件中设置的 CMake 文件路径是相对于你的库的 android 目录的。另外,还需要注意的是,当你修改配置文件时,你需要在测试应用中删除 android/build 目录,因为它保存了自动链接的缓存信息,这些信息可能不会自动更新,因此需要手动删除。

现在,你需要实际创建这个CMake文件——和之前一样,你可以从codegen生成的那个开始。关键是重新定义react_codegen_<CODEGEN名称>目标,并将你的自定义代码与生成的代码一起包含进去。为了确保自定义代码会被使用而不是生成的代码,包含自定义代码的目录需要首先设置在包含路径中。

    # CMakeLists.txt配置文件  
    cmake_minimum_required(VERSION 3.13)  # 设置CMake最低版本要求为3.13  
    project(NewArchTricks)  # 项目名称为NewArchTricks  

    set(CMAKE_VERBOSE_MAKEFILE on)  # 设置详细的构建日志  
    set(CMAKE_CXX_STANDARD 20)  # 设置C++标准为20  

    file(GLOB_RECURSE new_arch_tricks_SRCS CONFIGURE_DEPENDS ../shared/*.cpp)  # 新架构技巧源文件  
    file(GLOB_RECURSE new_arch_tricks_codegen_SRCS CONFIGURE_DEPENDS ./build/generated/source/codegen/jni/*.cpp)  # 新架构代码生成源文件  

    add_library(  
      react_codegen_NewArchTricksSpec  # 反编译新架构特性的库  
      SHARED  
      ${new_arch_tricks_SRCS}  
      ${new_arch_tricks_codegen_SRCS}  
    )  

    target_include_directories(react_codegen_NewArchTricksSpec PUBLIC ../shared)  # 指定公共包含目录为../shared  
    target_include_directories(react_codegen_NewArchTricksSpec PUBLIC ./build/generated/source/codegen/jni)  # 指定公共包含目录为./build/generated/source/codegen/jni  

    target_link_libraries(  
      react_codegen_NewArchTricksSpec  # 将react_codegen_NewArchTricksSpec链接到以下库  
      fbjni  
      jsi  
      reactnative  
    )

最后一步是创建 ComponentDescriptors.hComponentDescriptors.cpp 文件——同样,你可以从生成的文件入手。你需要确保自定义组件描述器被包含在头文件中。头文件中定义的所有内容也将对 React Native 可用,并且 React Native 期望这些相关的描述符来自该文件。

    // ComponentDescriptors.h  
    #pragma once  
    #include <react/renderer/components/NewArchTricksSpec/ShadowNodes.h>  
    #include <react/renderer/core/ConcreteComponentDescriptor.h>  
    #include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>  
    #include <react/renderer/components/NewArchTricksSpec/NewArchTricksViewComponentDescriptor.h>  

    namespace facebook::react {  

    void NewArchTricksSpec_registerComponentDescriptorsFromCodegen(  
      std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);  

    } // namespace facebook::react
    // ComponentDescriptors.cpp  
    #include <react/renderer/components/NewArchTricksSpec/ComponentDescriptors.h>  
    #include <react/renderer/core/ConcreteComponentDescriptor.h>  
    #include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>  

    namespace facebook::react {  
    } // 结束

就这样!从现在起,每次渲染你的组件,它将使用你刚才创建的 shadow node,但实际上并没有什么实质性的变化。

想了解更多,请点击这里阅读React Native新架构的第二部分文章。

这只是好玩部分的前奏——请查看ShadowNode.hLayoutableShadowNode.h,以及YogaLayoutableShadowNode.h,看看你刚刚获得哪些字段和方法。

我们是Software Mansion:React Native核心开发人员、新架构专家、社区构建者、多媒体专家,以及软件开发顾问。您需要帮助完成项目吗?请联系我们,我们将很乐意帮助您:[email protected]

點擊查看更多內容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優質文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學習,寫下你的評論
感謝您的支持,我會繼續努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學

大額優惠券免費領

立即參與 放棄機會
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號

舉報

0/150
提交
取消