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 技术博客与社区

相关推荐
A-花开堪折几秒前
Android7 Input(十)View 处理Input事件pipeline
android·嵌入式硬件
Shujie_L1 小时前
Android基础回顾】六:安卓显示机制Surface 、 SurfaceFlinger、Choreographer
android
海棠一号1 小时前
Android Settings 数据库生成、监听与默认值配置
android·数据库
雨白2 小时前
Fragment 入门教程:从核心概念到实践操作
android
烈焰晴天2 小时前
使用ReactNative加载Svga动画支持三端【Android/IOS/Harmony】
android·react native·ios
阿幸软件杂货间2 小时前
PPT转图片拼贴工具 v2.0
android·python·powerpoint
sg_knight2 小时前
Flutter嵌入式开发实战 ——从树莓派到智能家居控制面板,打造工业级交互终端
android·前端·flutter·ios·智能家居·跨平台
Digitally3 小时前
如何轻松将视频从安卓设备传输到电脑?
android·电脑·音视频
Dola_Pan3 小时前
Android四大组件通讯指南:Kotlin版组件茶话会
android·开发语言·kotlin
hopetomorrow4 小时前
学习路之PHP--webman安装及使用
android·学习·php