ReactNative介绍及简化版原理实现

222 阅读11分钟

willian-justen-de-vasconcellos-RyoQe3BU8gI-unsplash.jpg

一、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是代表的是自绘引擎的技术方案
对比维度H5React NativeFlutter
技术特点
编程语言HTML/CSS/JSJavaScript/TypeScriptDart
运行方式WebView容器JS Bridge + 原生组件直接编译原生代码
UI渲染浏览器渲染原生组件渲染自绘UI引擎
性能表现
启动速度中等
运行性能较差良好优秀
动画流畅度一般流畅非常流畅
内存占用中等较低
开发体验
学习成本中等中高
开发效率
热重载即时刷新支持支持
调试工具浏览器DevToolsFlipper/ChromeFlutter 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代码,看看整体它是如何做的吧:

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进行转换为一下代码:

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来实现:

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侧来注入,再来看下代码:

// 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如何实现吧

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要做的事,就是渲染整颗树了。

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 ComponentsReact组件和业务逻辑执行• 组件状态管理 • 生命周期处理 • 用户交互逻辑 • 数据处理和API调用
Fiber ReconcilerReact的协调算法引擎• 虚拟DOM差异计算 • 可中断的渲染过程 • 优先级调度系统 • 时间切片(Time Slicing)
React Native FrameworkRN框架核心• 组件树管理 • 平台抽象层 • 事件系统封装 • 样式处理
JSI Layer
JavaScript Interface (JSI)JS与Native直接通信桥梁• 消除JSON序列化开销 • 同步函数调用 • 类型安全的绑定 • 内存共享机制
Hermes Engine专为RN优化的JS引擎• 字节码预编译 • 减少内存占用 • 提升启动速度 • 垃圾回收优化
Fabric (UI Layer)
Codegen静态代码生成工具• TypeScript接口分析 • C++绑定代码生成 • 类型定义自动化 • 编译时错误检查
Shadow Tree平台无关的UI树结构• 跨平台布局抽象 • 差异算法优化 • 布局状态管理 • 并发渲染支持
Yoga Layout EngineFlexbox布局计算引擎• 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 技术博客与社区