ReactNative介绍及简化版原理实现

一、React Native是什么?

React Native是Facebook在2015年开源的移动应用开发框架,它允许开发者使用JavaScript和React语法来构建原生移动应用。RN的核心理念是"Learn once, write anywhere"(一次学习,随处编写),通过JavaScript Bridge与原生平台通信,最终渲染为真正的原生组件。

1.1 出现时机

2015年3月,Facebook 在 F8 开发者大会上正式发布了 React Native,这个时期正是移动互联网快速发展的阶段,移动应用开发需求激增。

1.2 解决的问题

  1. 跨平台效率问题,iOS和Android两套代码
  2. 开发维护成本高
  3. 基于React实现一次编写,到处运行的理念

1.3 H5 vs RN vs Flutter

为什么要这么对比呢,因为这三个代表了三种不同的技术流派:

  1. h5对应的是所有基于webview的技术方案
  2. RN对应的是逻辑JS + Native Runtime的技术方案
  3. Flutter是代表的是自绘引擎的技术方案
对比维度 H5 React Native Flutter
技术特点
编程语言 HTML/CSS/JS JavaScript/TypeScript Dart
运行方式 WebView容器 JS Bridge + 原生组件 直接编译原生代码
UI渲染 浏览器渲染 原生组件渲染 自绘UI引擎
性能表现
启动速度 中等
运行性能 较差 良好 优秀
动画流畅度 一般 流畅 非常流畅
内存占用 中等 较低
开发体验
学习成本 中等 中高
开发效率
热重载 即时刷新 支持 支持
调试工具 浏览器DevTools Flipper/Chrome Flutter Inspector
生态与支持
生态丰富度 非常丰富 丰富 快速增长
第三方库 海量 较多 中等
社区活跃度 非常活跃 活跃 很活跃
大厂支持 全平台 Meta/微软 Google
部署运维
更新方式 即时更新 热更新 发版更新
包体积 中等 较大
兼容性 浏览器兼容性 iOS/Android 跨多平台
维护成本 中等 中等
适用场景
内容展示 ✅ 最佳 ⚠️ 过度 ⚠️ 过度
复杂应用 ❌ 性能限制 ✅ 适合 ✅ 很适合
游戏开发 ❌ 不适合 ⚠️ 简单游戏 ✅ 适合
快速原型 ✅ 最快 ✅ 快 ✅ 快
成本考量
开发成本 中等 中等
人员要求 Web前端 前端+移动端 需学Dart
测试成本 中等 中等

1.4 成功案例

reactnative.dev/showcase

APP Store中 Top 100 iOS app选择的技术栈:x.com/search?q=re...

国外知名应用:

  • Facebook、Instagram、WhatsApp (Meta系)
  • Microsoft Office、Skype (微软系)
  • Tesla、Uber Eats、Pinterest
  • Discord、Shopify、Bloomberg

国内主要厂商:

  • 字节跳动: 抖音、今日头条 (基于LYNX引擎)
  • 腾讯: 微信,视频等
  • 美团: 基于MRN框架
  • 京东: 京东App部分模块
  • 携程: 基于CRN框架

市场数据: 根据2024年统计,App Store Top 500应用中约有15%使用了React Native技术栈。

以上我们对比了不同的技术方案,那我们会好奇,RN是如何来做的呢,既能保持动态化,又可以保证性能呢?国内大厂例如,国外的微软,facebook,特斯拉,都是RN的重度使用用户为什么它是各个大厂动态化的基础呢?

二、动态化原理

RN的工作原理就像上面介绍的一样是,JS + Native渲染,渲染的节点从前端的dom元素,变成了原生的TextView, ImageView等原生View,这样开发者就只需要学习React即可开发,那对于我们移动端开发人员来说,我们就会好奇它是如何实现的呢?那我们就可以从这里入手来看下RN最核心的知识JS如何绑定Native渲染来看下吧。

2.1 简化版RN架构

说了这么多,都是基础结构知识,那有没有一种直观的方式让我们来了解整体RN的原理呢,正如Linus Torvalds所说:'Talk is cheap. Show me the code.'接下来我们将通过构建一个简化版的RN框架,来深入理解其核心工作原理。

主要参考资料和文章:

我们可以从架构来看如何设计:

我们可以套用原来WebviewBridge的知识,那动态化需要一个桥用来连接js语言和Native语言,还要能执行js代码,那我们不依赖于webview的话就需要一个js虚拟机,这里我们选择的是QuickJS:github.com/taoweiji/qu...;

没有了webview的渲染,需要用Native渲染,那就需要一个Native渲染的Runtime,还要支持灵活注册

我们来看下我们示例的react代码,看看整体它是如何做的吧:

java 复制代码
function App() {
  return (
    <View 
      style={{
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#f5f5f5"
      }}
    >
      <Text
        onPress={() => {
          console.info('文本被点击了!');
        }}
        style={{
          fontSize: 24,
          color: "#007AFF",
          padding: 20
        }}
      >
        点击我
      </Text>
    </View>
  );
}

React.render(APP);

这里都是JSX语法,然后我们可以通过babel进行转换为一下代码:

java 复制代码
function App() {
  return /*#__PURE__*/React.createElement(View, {
    style: {
      flex: 1,
      justifyContent: "center",
      alignItems: "center",
      backgroundColor: "#f5f5f5"
    }
  }, /*#__PURE__*/React.createElement(Text, {
    onPress: () => {
      console.info('文本被点击了!');
    },
    style: {
      fontSize: 24,
      color: "#007AFF",
      padding: 20
    }
  }, "\u70B9\u51FB\u6211"));
}
React.render(/*#__PURE__*/React.createElement(App, null));

我们能看到其实这种标签写法就是语法糖,真正的调用方法是React.createElement。

从这里面我们可以看到我们需要解决两个问题,一个就是逻辑动态化,一个就是如何通过标签渲染为Native View,先来看看如何做逻辑动态化吧。

2.1.1 逻辑动态化如何做

我们是基于QuickJS来进行执行JS代码,它就像给webview写桥一样,本身就支持,具体教程在这里github.com/taoweiji/qu...

2.1.2 创建QuickJS环境

通过以上文档我们可以看到主要通过一下API来实现:

java 复制代码
class JsContext {
    // 创建一个引擎
    private val mEngine = QuickJS.createRuntime()
    private var mContext: JSContext? = null

    init {
        // 创建一个上下文,一个引擎可以创建多个,这里我们只用一个就好
        mContext = mEngine.createContext()
    }

    fun getContext(): JSContext = mContext!!

    fun runApplication(jsBundle: JsBundle) {
        mContext!!.executeStringScript(jsBundle.mAppJavaScript, "test.js")
    }

}

这样我么可以看到通过创建好引擎后,直接调用executeStringScript就可以执行JS代码

2.1.3 注入Native方法

那我们执行JS后就有一个问题,以console.info为例,它在JS侧没有定义的,需要通过native侧来注入,再来看下代码:

java 复制代码
// 1.context就是我们上面创建的QuickJS的一个上下文
val consoleObj = JSObject(context)
// 2.具体的native侧方法
consoleObj.registerJavaMethod(object : JavaCallback {
    override fun invoke(receiver: JSObject?, args: JSArray?): Any? {
        printJSArray(args)
    }
}, "info")
// 3.注入到console对象上
context.set("console", consoleObj)

// 4.具体实现逻辑
private fun printJSArray(params: JSArray?) {
    if (params == null) return
    
    val messages = mutableListOf<String>()
    for (i in 0 until params.length()) {
        try {
            val value = params.getString(i) ?: "null"
            messages.add(value)
        } catch (e: Exception) {
            messages.add("[object]")
        }
    }
    
    Log.i("JSConsole", messages.joinToString(" "))
}

从以上代码上我们就可以看到我们实现了JS->Native的逻辑。

2.1.4 UI动态化-构建UI树

逻辑动态化我们上面就介绍完毕了,那如何实现UI动态化呢?从上面我们可以看到渲染和两个方法有关系一个是React.createElement另一个是React.render,React.render负责渲染,而React.createElement负责创建视图树,createElement已经给我们抽象好了固定为(类型+参数+child),那我们就实现这两个方法就好了

先来看下createElement如何实现吧

java 复制代码
private fun createElement(params: JSArray?): JSObject? {
        if (params == null || params.length() < 1) {
            Log.e("ReactModule", "createElement requires at least 1 parameter")
            return null
        }
        
        
        try {
            // 1.要进行转换,创建一个vElement对象
            val vElement = JSObject(jsContext)
            
            // 组件类型
            val type = params.getString(0)
            if (type.isNullOrEmpty()) {
                Log.e("ReactModule", "Component type cannot be null or empty")
                return null
            }
            // 2.装配类型
            vElement.set("type", type)
            
            // props(可选)
            val props = if (params.length() > 1) {
                params.getObject(1) ?: JSObject(jsContext)
            } else {
                JSObject(jsContext)
            }
            // 3.装配参数
            vElement.set("props", props)
            
            
            // children
            val children = JSObject(jsContext)
            var childCount = 0

            // 4.解析child
            for (i in 2 until params.length()) {
                val objectChild = params.getObject(i)
                val stringChild = if (objectChild == null) params.getString(i) else null
                
                when {
                    objectChild != null -> {
                        children.set(childCount.toString(), objectChild)
                        childCount++
                    }
                    stringChild != null -> {
                        children.set(childCount.toString(), stringChild)
                        childCount++
                    }
                }
            }
            
            children.set("length", childCount)
            // 5.装配child组件
            vElement.set("children", children)
            
            return vElement
            
        } catch (e: Exception) {
            Log.e("ReactModule", "Error creating element", e)
            return null
        }
    }

从这里代码看出来我们就是在构建一个渲染树,构建渲染树结束后,js侧在调用render方法进行渲染,接着我们看下渲染树如何做吧。

2.1.5 UI动态化-渲染UI树

从上面我们可以知道我们已经构建了一个UI树,剩下render要做的事,就是渲染整颗树了。

java 复制代码
private fun render(params: JSArray?): Any? {
    try {
        val vdom = params.getObject(0) ?: return null

        //1.要基于Android侧根containerView来做
        containerView?.let { container ->
            renderer.renderRoot(vdom, container, jsContext)
            return true
        } ?: run {
            Log.e("ReactModule", "Container not set")
            return false
        }
        
    } catch (e: Exception) {
        Log.e("ReactModule", "Error rendering virtual DOM", e)
        return false
    }
}

fun render(vElement: JSObject, parent: ViewGroup, jsContext: JSContext): View? {
    try {
        val type = vElement.getString("type")
        if (type.isNullOrEmpty()) {
            Log.e("VirtualDOMRenderer", "Element type is null or empty")
            return null
        }
        
        val props = vElement.getObject("props")
        val children = vElement.getObject("children")

        //2.将视图树从JS转换为NativeView,支持View和Text
        val renderer = RendererFactory.createRenderer(type, context)
        if (renderer == null) {
            Log.e("VirtualDOMRenderer", "No renderer found for type: $type")
            return null
        }
        //4.触发真正的渲染
        return renderer.render(props, children, parent, jsContext)
        
    } catch (e: Exception) {
        Log.e("VirtualDOMRenderer", "Error rendering virtual DOM element", e)
        return null
    }
}

//3.创建Text -> Native Text的映射
class TextRenderer(private val context: Context) : IComponentRenderer {
    
    private val styleProcessor = TextStyleProcessor()
    private val eventHandler = TextEventHandler()
    
    override fun getSupportedType(): String = "Text"

    //5.渲染Text
    override fun render(props: JSObject?, children: JSObject?, parent: ViewGroup, jsContext: JSContext): View {
        val textView = TextView(context).apply {
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
        }
        
        // 设置文本内容
        children?.let { childrenObj ->
            val lengthValue = try {
                childrenObj.getString("length")?.toIntOrNull() ?: 0
            } catch (e: Exception) {
                0
            }
            
            if (lengthValue > 0) {
                val text = childrenObj.getString("0")
                textView.text = text ?: ""
            }
        }
        
        // 应用样式
        props?.getObject("style")?.let { style ->
            styleProcessor.applyStyle(textView, style)
        }
        
        // 绑定事件
        eventHandler.bindEvents(textView, props, jsContext)
        
        return textView
    }
} 

// 6.JS中View组件相当于Android的ViewGroup,这个是将NativeView树整体串联的地方
    override fun render(props: JSObject?, children: JSObject?, parent: ViewGroup, jsContext: JSContext): View {
        val linearLayout = LinearLayout(context).apply {
            orientation = LinearLayout.VERTICAL
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
        }
        
        // 应用样式
        props?.getObject("style")?.let { style ->
            styleProcessor.applyStyle(linearLayout, style)
        }
        
        // 渲染子组件
        children?.let { childrenObj ->
            val length = try {
                childrenObj.getString("length")?.toIntOrNull() ?: 0
            } catch (e: Exception) {
                0
            }
            
            for (i in 0 until length) {
                val child = childrenObj.getObject(i.toString())
                child?.let { childElement ->
                    val childType = childElement.getString("type")
                    val childRenderer = RendererFactory.createRenderer(childType ?: "", context)
                    
                    childRenderer?.let { renderer ->
                        val childProps = childElement.getObject("props")
                        val childChildren = childElement.getObject("children")
                        val childView = renderer.render(childProps, childChildren, linearLayout, jsContext)
                        linearLayout.addView(childView)
                    }
                }
            }
        }
        
        return linearLayout
    }

2.1.6 效果

最终在页面上的效果就是如图所示:

以上我们就完整的实现了利用QuickJS+Native视图树渲染的流程,真正的ReactNative要复杂的多,但是ReactNative框架整体核心流程是如此的。

源码在:github.com/hellokai55/...

三、RN整体架构

RN发找到现在已经很庞大了,整体架构设计的部分有很多,简单看下整体RN的架构图,以及其中的核心技术点我们来看下:

技术组件 作用与职责 核心功能
JavaScript Thread
JavaScript Code/React Components React组件和业务逻辑执行 • 组件状态管理 • 生命周期处理 • 用户交互逻辑 • 数据处理和API调用
Fiber Reconciler React的协调算法引擎 • 虚拟DOM差异计算 • 可中断的渲染过程 • 优先级调度系统 • 时间切片(Time Slicing)
React Native Framework RN框架核心 • 组件树管理 • 平台抽象层 • 事件系统封装 • 样式处理
JSI Layer
JavaScript Interface (JSI) JS与Native直接通信桥梁 • 消除JSON序列化开销 • 同步函数调用 • 类型安全的绑定 • 内存共享机制
Hermes Engine 专为RN优化的JS引擎 • 字节码预编译 • 减少内存占用 • 提升启动速度 • 垃圾回收优化
Fabric (UI Layer)
Codegen 静态代码生成工具 • TypeScript接口分析 • C++绑定代码生成 • 类型定义自动化 • 编译时错误检查
Shadow Tree 平台无关的UI树结构 • 跨平台布局抽象 • 差异算法优化 • 布局状态管理 • 并发渲染支持
Yoga Layout Engine Flexbox布局计算引擎 • CSS Flexbox实现 • 跨平台布局算法 • 约束求解 • 性能优化的C++实现
Background Thread
Fabric Renderer 新架构渲染器 • Shadow Node创建 • 布局计算调度 • 树状结构差异对比 • 并发渲染管理
Layout Engine 布局计算处理器 • Measure阶段处理 • Layout阶段执行 • 约束条件求解 • 布局缓存优化
Main Thread (UI Thread)
Mounting Layer 平台视图挂载层 • 原生View创建 • 视图更新应用 • 事件绑定管理 • 生命周期处理
Native Views 平台原生视图组件 • iOS UIView封装 • Android View封装 • 平台特性支持 • 原生动画集成
Event System 事件处理系统 • 触摸事件捕获 • 手势识别 • 事件冒泡处理 • 跨平台事件标准化
TurboModules
TurboModule 新一代原生模块系统 • 懒加载机制 • 类型安全保证 • 更好的性能表现 • 同步/异步调用支持
Native Modules 平台原生功能模块 • 平台API封装 • 第三方库集成 • 设备功能访问 • 自定义功能扩展

四、后续RN参考文章链接

4.1 官方文档与资源

4.2 新架构相关

4.3 核心技术深度文章

4.4 Skia渲染相关

4.5 大厂实践案例

4.6 YouTube技术视频

4.7 开源项目与工具

4.8 性能监控与调试

4.9 热更新方案

4.10 跨平台扩展

4.11 技术博客与社区

相关推荐
还鮟3 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡4 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi004 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil6 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你6 小时前
Android View的绘制原理详解
android
移动开发者1号9 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号9 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best14 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk14 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
wen's17 小时前
React Native 0.79.4 中 [RCTView setColor:] 崩溃问题完整解决方案
javascript·react native·react.js