V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
leia
V2EX  ›  前端开发

React-native 的新架构

  •  
  •   leia · 8 天前 · 800 次点击

    本文总结:

    文章主要介绍了 ​React Native 的新架构​,包括以下几个方面的内容:📱✨

    • 如何​抹平 iOS 和 Android 样式差异​,提升跨平台一致性;
    • 分析了​旧架构中存在的问题​,如通信瓶颈、启动慢、维护复杂等;
    • 介绍了 JSI 中间层 带来的变革,如:
      • 不再强依赖 JavaScriptCore 引擎​;
      • 可​直接在 JS 层调用 Native 方法​,提升交互效率;
    • 讲解了 RN 的​渲染阶段流程​;
    • 常用基础库(如 ​React Navigation​)的配套使用;
    • 异常捕捉机制热更新/包体更新的思路。

    🔍 关联问题与亮点:

    • 新架构优势是什么? 新架构通过引入 JSI 、Fabric 和 TurboModules ,解决了旧通信机制的性能瓶颈,提升了启动速度和运行效率。
    • Fabric 如何优化渲染?Fabric 渲染引擎让渲染流程更接近 React 生态,通过异步渲染、协调与 Commit 阶段的优化,实现更流畅的 UI 体验。🌈
    • Turbo Modules 怎样加速?TurboModules 通过 按需加载、延迟初始化 方式提升模块加载性能,且与 JSI 结合使 JS 层调用 Native 层更加轻量、直接。⚡

    1 、React-native 的 style

    上一篇文章,我们应该已经对跨平台有了一定的概念,但这里其实有一个问题并没有解决,就是其实在ios 和安卓上的样式是有差异的,那么我们的Rn就需要去抹平这种差异化,rn中采用的是css-in-js

    我们在js中写的style对象,将由单独的一个线程去处理,也就是Shadow thread

    在这个线程中由Yoga引擎(这也是 facebook 开发的)重新去计算app的布局,这个引擎在计算了有关app的东西后,将结果又反馈至UI线程,最终呈现出来。

    那么一个完整的老版本的架构是这样的:

    然后我们现在把整个流程理一下: 假设我们现在有一段react的代码

    <View style={{width: 200, height: 200}}/>
    

    下一步就是js线程将其序列化

    UIManager.createView([352,"RCTView",191,{width":200,"height":200}])
    

    而此时这个task进入到了桥前的异步队列中,它的目的地是ShadowThread,ShadowTread接收到这条信息后,先反序列化,形成Shadow tree,然后传给Yoga,形成原生布局信息。

    下一步又先序列化把信息传给native线程,然后拿到后反序列化根据布局信息去进行渲染和绘制。

    大伙现在应该已经对一个rn的整体架构有了基本的了解,还记得上篇文章的问题吗?负载和异步会导致性能问题和不确定性。

    • 线程信息的传递因为要减小开销每次都需要反复序列化,但序列化又是一个消耗很大的事情。
    • 异步队列的不确定性,你并不能保证一个事件的顺序。

    因此rn的新架构就是要去解决这些问题,也就是现在的中间层。

    2 、React-native 新架构

    关于新架构的内容很多,可能有些地方我自己也有理解不当的地方欢迎指正。

    我们先讲讲最大的改动,就rn在新架构中直接把老的桥干掉了,直接换成了一个新的中间层或者说通用层,也就是 JS Interface (JSI)

    在这个通用层里面有很多的新内容我们可以先看一下这个架构图。

    So ,我们来看看有哪些变化,上面的图中间部分,就是JSI。(解释一下为啥这个图是这样的,因为就Turbo Modules我其实认为是Native Moudles的加强,而FabricRenderer的加强,他们是原本就存在的)。

    1 、JS-bundle不再强依赖JavaScriptCore引擎了。我们现在可以很方便用更好的引擎去替换了,性能更好了。比如Hermes

    2 、JSI让我们可以直接在js层调用native的方法了。由HostObject C++ object实现,它直接存储了native层方法和属性的引用放在了一个全局对象上,然后我们js就可以直接调用java/oc 的 api

    3 、Turbo Modules的出现(上图中的 Native Moudles ),在之前的架构中 JS 使用的所有 Native Modules(例如蓝牙、地理位置、文件存储等)都必须在应用程序打开之前进行初始化,这意味着即使用户不需要某些模块,但是它仍然必须在启动时进行初始化。

    Turbo Modules 基本上是对这些旧的 Native 模块的增强,正如在前面介绍的那样,现在 JS 将能够持有这些模块的引用,所以 JS 代码可以仅在需要时才加载对应模块,这样可以将显着缩短 RN 应用的启动时间。

    4 、Fabric也就是上图中的renderer(以前 shadow 层是在 native 层实现的),一个新的UI 渲染器,它就相当于在 c++中,可以直接创建一个 ShadowTree,一个就是快,同时也减少了渲染元素的步骤。

    可能大家没懂,举个例子:当 App 运行时,React 会执行你的代码并在 JS 中创建一个 ReactElementTree ,基于这棵树渲染器会在 C++ 中创建一个 ReactShadowTree

    Fabric 会使用 Shadow Tree 来计算 UI 元素的位置,而一旦 Layout 完成,Shadow Tree 就会被转换为由 Native Elements 组成的 HostViewTree(例如:RN 里的 会变成 Android 中的 ViewGroup 和 iOS 中的 UIView )。

    5 、 codegen其实就是一个静态类型检查器,CodeGen使用类型确定后的 JavaScript 来为Turbo ModulesFabric定义供他们使用的接口元素,并且它会在编译时生成更多的native代码,而非运行时。

    3 、RN 的渲染

    将 React 代码渲染到宿主平台,我们称为渲染流水线,可大致分为三个阶段:

    • 渲染(Render):在 JavaScript 中,React 执行那些业务逻辑代码创建 React 元素树( React Element Trees )。然后在 C++ 中,用 React 元素树创建 React 影子树( React Shadow Tree )。
    • 提交(Commit):在 React 影子树完全创建后,渲染器会触发一次提交。这会将 React 元素树和新创建的 React 影子树的提升为“下一棵要挂载的树”。 这个过程中也包括了布局信息计算。
    • 挂载(Mount):React 影子树有了布局计算结果后,它会被转化为一个宿主视图树(Host View Tree)。

    4 、一些基本的库

    Ok ,上面都是框架的架构设计,我们先有一个大体的概念,那么现在我们稍微走近实战去了解一些必要的包,因为后面不会怎么讲。

    React-native只内置了一些必要的包,但为了尽可能的减小包的大小,许多的包需要你自己去配置,例如:asyncStorage,这种sdk你需要一点点依赖相关的原生知识,但问题不大,一般都会有模版去教你,照着模版就行了(但也不一定,绝大数情况是)。那么我们现在就看看一些常用的包。

    4.1 、React Navigation

    这个应该几乎是每个用rn的同学都该了解的东西了,原生appweb的路由是不同的,在app里其实是没有url这种概念,在原生里要理解screen,也就是说控制用户所见屏幕。

    在老版本rn有一些原始导航组件去控制屏幕,但很复杂,所以就现在一般都会用到react-navigation这个库。

    我直接上实战吧,

    import * as React from "react";
    
    import { NavigationContainer } from "@react-navigation/native";
    
    import { createNativeStackNavigator } from "@react-navigation/native-stack";
    
    import Home from "./Home";
    
    import Settings from "./Settings";
    
    const Stack = createNativeStackNavigator();
    
    export default function App() {
    
    return (
    
        <NavigationContainer>
    
            <Stack.Navigator>
              <Stack.Screen name="Home" component={Home} />
    
                <Stack.Screen name="Settings" component={Settings}/>
    
            </Stack.Navigator>
    
        </NavigationContainer>
    
    );
    
    }
    

    createNativeStackNavigator 是创建你的导航组件的一个方法,它返回一个对象,里面有ScreenNavigator2 个组件,他们用来配置导航

    import React from "react";
    
    import { View, Text, Button, StatusBar } from "react-native";
    
    import styles from "./styles";
    export default function Home({ navigation }) {
    
    return (
    
    <View style={styles.container}>
    
    <StatusBar barStyle="dark-content" />
    
    <Text>Home Screen</Text>
    
    <Button
    
    title="Settings"
    
    onPress={() => navigation.navigate("Settings")}
    
    />
    
    </View>
    
    );
    
    }
    

    就看到home组件,当你按下的时候就跳转到settings这个屏幕上去,更多的内容我们后面实战的时候再讲吧,只是做个简单的演示。

    4.2 RN 组件库

    antd mobile估计国内我们基本用的都这个或者就是自己封装的组件库,推荐几个其他的NativeBase、React Native ElementUI KitternReact-native-paper

    4.3 启动页

    其实启动页就是你js线程启动前展示的过度页面,React-native-bootsplash

    4.4 Icon

    react-native-vector-iconsreact-native-svg

    4.5 异常捕捉

    通常,当我们开发一个 web 应用时,我们很好处理错误,因为它们不会超出JS的范围。简单的说我们前端就是web的王(掌控力),我们可以很容易地看到原因,并在DevTools中打开日志。 但Rn因为除了环境的 JS 之外,我们还有native组件,这也可能导致app执行中的错误。因此,当发生错误时,我们的应用程序将关闭立即我们很难弄清楚原因,因此React-native-exception-handler也正是解决这个问题的包。 就像这样:

    import { setJSExceptionHandler, setNativeExceptionHandler } 
    from "react-native-exception-handler";
    setJSExceptionHandler((error, isFatal) => {
    
    });
    
    const exceptionhandler = (exceptionString) => {
    
    };
    
    setNativeExceptionHandler(
    
    exceptionhandler,
    
    forceAppQuit,
    
    executeDefaultHandler
    
    );
    

    4.6 包更新

    其实如果是 ios 我们要更新应用上传到商店,有这么个技术OAT可以替换 js 包,就可以看看微软的Codepush

    4.结束

    [rn 中文文档地址],就这 2 篇文章都是在理一些基础理论的东西,对于一些组件 api 大伙可以看看文档。

    欢迎加入群聊,我们一起讨论一些更有趣的技术、商业、闲聊。

    3 条回复    2025-05-29 02:13:52 +08:00
    NewYear
        1
    NewYear  
       8 天前
    你可以大胆一点。

    React Native 已经支持桌面端了,你这个内容还在围绕“​抹平 iOS 和 Android 样式差异​”,这就很不吸引人。
    leia
        2
    leia  
    OP
       8 天前
    @NewYear 感谢评价分享,
    leia
        3
    leia  
    OP
       8 天前
    @NewYear 确实是 这个文章是在半年前写的 👍感谢评价
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2665 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 11:24 · PVG 19:24 · LAX 04:24 · JFK 07:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.