爱奇艺RND框架之JS Framework解析
ccwgpt 2024-10-15 08:52 27 浏览 0 评论
背景介绍
RND,全称React Node Desktop,起源于RN在爱奇艺PC端的实现,采用React JS framework + node.JS runtime + native UI engine架构,目标是成为最轻量的JS开发桌面应用的跨平台方案。目前爱奇艺PC客户端的大多数页面都是基于RND开发的。
传统的JS开发native应用的方案都是将native组件注入到JS,JS会按照native的开发模式开发应用,更多的是开发语言从C++换到了JS,开发思想还是native的。React JS带来了全新的开发思路,非常好地隔离了JS层和native层,业务开发基于React JS开发范式而不用受native约束。为了适配自研的lyra引擎以及为业务层提供更方便的开发设施,团队对React JS Framework做了深度的适配,接下来将带着大家深入了解React JS Framework,帮助大家理解这个优雅的view层框架。
React Fiber
撰写本篇文章时RN的最新版本是0.57.8,团队适配的RN版本是0.51.0,它依赖的React版本是16.0.0。本文主要针对0.51.0进行说明,0.51.0与0.57.8的差别不大,基本原理是一样的。React 16除了将备受争议的BSD+Patents协议改为MIT协议之外,还带来了许多新特性,比如:
1.允许在render函数中返回节点数组
2.提供更好的错误处理机制:componentDidCatch
3.支持自定义DOM属性
但最关键的一点还是React16是一次重写,在保持API不变的情况下,将核心架构改为了Fiber。在介绍React的Fiber架构之前,首先介绍几个概念:
1.React为每种类型的节点都分配了一个数字编号,具体为:
2.每一个控件都会对应一个fiber对象节点,所有的fiber节点就构成了virtualdom树,fiber对象的结构和表示的意义如下:
3.一个完整的virtualdom树的结构如下:
节点树主要分为创建和更新两个过程,每个过程都可以分为以下四个阶段:
节点的创建
- 初始化阶段
RN程序的入口如下:
其中Index是需要渲染的根控件,runApplication函数会调用ReactNative.render函数,将业务的根节点包裹在AppContainer控件中传递给该函数:
其中RootComponent就是注册的根控件,AppContainer是RN提供的自定义控件。在该函数中会创建一个HostRoot节点,该节点挂载在root对象的current属性上,root对象就是整个节点树的根。接着会调用addTopLevelUpdate主动生成一个update,待更新数据就是传给ReactNative.render的参数,这个过程可以看做是RN内部主动调用了setState函数。然后调用scheduleUpdate函数将待更新的根节点保存在nextScheduledRoot变量中。在RN中更新都是从根节点开始的,无论setState函数在哪个控件上调用。最后调用performWork函数进入workloop阶段。
- workloop阶段
该阶段包含的主要函数及其调用关系如下:
首先根据HostRoot节点创建待操作节点(注意:RN不是直接处理当前节点,而是处理当前节点的拷贝,也就是节点的alternate属性),然后从该节点开始根据节点类型处理当前节点,也就是beginWork中的各种update方法,并且生成下一个待处理的节点,赋值给nextUnitOfWork。如果nextUnitOfWork不为空,就对其进行处理,否则执行completeUnitOfWork,然后依次遍历处理该节点的兄弟节点和父节点的兄弟节点,直到父节点为空,循环结束。不难发现RN是按照深度优先来创建和更新节点的。具体的创建顺序如下图所示:
节点的update操作包含了对新节点的创建和已有节点更新两种情况,alternate为空是创建。节点的处理主要包括三种:自定义节点、根节点以及基础控件节点。节点的创建和更新都是在响应节点的update函数中。下面主要针对这三种节点进行说明:
1.updateHostRoot
HostRoot节点不暴露给业务层,是RN内部使用的。前面说过在创建阶段addTopLevelUpdate函数中会生成一个update,保存在节点的updateQueue属性中,就是通过判断这个属性是否为空来区分是创建节点还是更新节点。如果updateQueue不为空,则取出AppContainer,开始创建AppContainer的fiber节点;如果updateQueue为空(更新阶段)就直接调用bailoutOnAlreadyFinishedWork获取已经创建好的AppContainer的fiber节点返回。
2.updateClassComponent
ClassComponent是复合控件,也就是通过React.createClass(es5写法)函数创建的控件。控件的创建阶段主要执行三个函数:
(1)constructClassInstance:
从fiber节点的type属性中取得控件的构造函数,然后创建一个实例,保存在控件fiber节点的stateNode属性中;
(2)mountClassInstance:
执行实例的componentWillMount函数,如果实例的componentDidMount存在,更新effectTag,待所有子节点处理完毕后再执行;
(3)finishClassComponent:
先执行实例的render函数,然后根据render函数中的返回值执行reconcileChildren函数创建对应的fiber节点。
在更新阶段则会调用下面两个函数:
(1)updateClassInstance:
判断控件是否需要更新shouldUpdate;
根据updateQueue,计算新的state;
存在生命周期函数时标记effectTag
(2)finishClassComponent:
不需要更新时cloneChildFibers;
需要更新时执行instance.render,然后执行reconcileChildren
3.updateHostComponent
这个函数中做的工作很少,在创建阶段调用reconcileChildren函数创建子节点的fiber节点并返回,更新阶段调用cloneChildFibers函数复制子节点并返回。在处理节点过程中,如果遇到节点的子节点为空,那么就会调用completeUnitOfWork函数。该函数根据节点类型进行相应处理:
如果是HostComponent,在创建阶段就会创建实例(createInstance),生成_nativeTag,生成createView命令,并且将子节点的HostComponent添加到实例的children属性中,发送setChildren命令添加节点,在更新阶段则标记是否需要更新。如果是ClassComponent节点则无需处理,因为在update阶段已经处理完毕。根据节点的effectTag值,向节点的firstEffect、nextEffect、lastEffect赋值,节点的firstEffect、nextEffect、lastEffect组成一个单链表结构,父节点会继承子节点的相应属性值,这些值会在接下来的commitAllWork阶段被处理。
- commitAllWork
workloop阶段执行完毕就进入到commitAllWork阶段。该阶段会调用以下两个函数:
1.commitAllHostEffects
由节点的firstEffect开始遍历,根据effectTag值进行相应的操作,节点更新、插入、删除等。
2.commitAllLifeCycles
改变root.current的值;执行生命周期函数:componentDidMount、componentDidUpdate等;执行ref函数。
节点的更新
节点更新阶段的处理流程如下图:
节点的update函数中已经包含了节点的创建和更新两种情况。这里主要说一下更新的流程和初始化阶段。节点的更新一般是通过实例的setState函数触发的,setState函数会调用Updater.enqueueSetState函数将需要更新的数据保存在fiber节点的updateQueue属性中,然后从当前节点开始向上更新父节点的优先级,更新到根节点结束。然后从根节点开始进入workloop和commitAllWork阶段。
通信机制
RND中JS层与native层的通信与RN是类似的,具体的通信机制如下所示:
JS端的messageQueue模块负责消息的接收和发送,JS端产生的命令会存储在messageQueue中,最后通过调用native向JS注入的接口函数将命令发送到native端,native端的BatchedBridge类负责接收处理JS命令。JS端将messageQueue的实例挂载到global对象上,native就能通过global对象访问到messageQueue中的所有实例属性和方法。native通过EventEmitter类将消息发送到JS端,实现方式为在JS运行环境中获取到messageQueue实例动态执行代码段。类似于浏览器中的window.eval函数的功能。这样就完成了JS和native端的双向通信。
优化与扩展
RND在JS层面还进行了一些优化和扩展,主要集中在bundle拆分、css3动画支持、脚手架工具、typescript声明文件扩展等方面。
RN的bundle体积很大,在有多个页面实例时尤为突出。因此考虑将RN框架代码单独分离出来,形成公共的base bundle,而将各页面的业务代码打包成各个jobbundle,从而减少了安装包体积和线上更新时的流量消耗。
我们在RND中增加了css3动画支持,应用程序可通过在style中指定符合css3动画规范的animation属性,即可实现高性能的动画效果。
类似于RN的react-native run-android命令,RND还扩展支持了run-desktop等脚手架命令。最后,我们也为RND提供了ts声明文件,支持开发者使用ts进行开发。
未来团队还将陆续为RND增加一些新的组件和API,特别是与桌面开发相关的特性,例如WindowComponent、Shell API、File API等。
结语
至此React JS Framework的整个处理流程大致都说完了,本文目的是希望起到一个抛砖引玉的作用。对于框架源码的分析有助于对框架本身有更深的理解,这样才能发现其本身的优点以及缺点,才能让我们在特定的使用场景中去取舍设计方案,去迭代、去优化、去创新。
RND在爱奇艺客户端的成功实践表明,RN同样适用于以运营内容为主的、迭代周期密集的互联网桌面应用,JS非常适合UI和业务逻辑的快速开发。随着各大JS引擎性能不断的优化,很多大厂都推出了基于JS语言的轻量级高性能App应用框架,可以预测在不久的将来,以内容运营为主的桌面产品上,JS很快会成为最受开发者欢迎的语言之一。
相关推荐
- RACI矩阵:项目管理中的角色与责任分配利器
-
作者:赵小燕RACI矩阵RACI矩阵是项目管理中的一种重要工具,旨在明确团队在各个任务中的角色和职责。通过将每个角色划分为负责人、最终责任人、咨询人和知情人四种类型,RACI矩阵确保每个人都清楚自己...
- 在弱矩阵组织中,如何做好项目管理工作?「慕哲制图」
-
慕哲出品必属精品系列在弱矩阵组织中,如何做好项目管理工作?【慕哲制图】-------------------------------慕哲制图系列0:一图掌握项目、项目集、项目组合、P2、商业分析和NP...
- Scrum模式:每日站会(Daily Scrum)
-
定义每日站会(DailyScrum)是一个Scrum团队在进行Sprint期间的日常会议。这个会议的主要目的是为了应对Sprint计划中的不断变化,确保团队能够有效应对挑战并达成Sprint目标。为...
- 大家都在谈论的敏捷开发&Scrum,到底是什么?
-
敏捷开发作为一种开发模式,近年来深受研发团队欢迎,与瀑布式开发相比,敏捷开发更轻量,灵活性更高,在当下多变环境下,越来越多团队选择敏捷开发。什么是敏捷?敏捷是一种在不确定和变化的环境中,通过创造和响应...
- 敏捷与Scrum是什么?(scrum敏捷开发是什么)
-
敏捷是一种思维模式和哲学,它描述了敏捷宣言中的一系列原则。另一方面,Scrum是一个框架,规定了实现这种思维方式的角色,事件,工件和规则/指南。换句话说,敏捷是思维方式,Scrum是规定实施敏捷哲学的...
- 敏捷项目管理与敏捷:Scrum流程图一览
-
敏捷开发中的Scrum流程通常可以用一个简单的流程图来表示,以便更清晰地展示Scrum框架的各个阶段和活动。以下是一个常见的Scrum流程图示例:这个流程图涵盖了Scrum框架的主要阶段和活动,其中包...
- Mockito 的最佳实践(mock方法)
-
记得以前面试的时候,面试官问我,平常开发过程中自己会不会测试?我回答当然会呀,自己写的代码怎么不测呢。现在想想我好像误会他的意思了,他应该是想问我关于单元测试,集成测试以及背后相关的知识,然而当时说到...
- EffectiveJava-5-枚举和注解(java枚举的作用与好处)
-
用enum代替int常量1.int枚举:引入枚举前,一般是声明一组具名的int常量,每个常量代表一个类型成员,这种方法叫做int枚举模式。int枚举模式是类型不安全的,例如下面两组常量:性别和动物种...
- Maven 干货 全篇共:28232 字。预计阅读时间:110 分钟。建议收藏!
-
Maven简介Maven这个词可以翻译为“知识的积累”,也可以翻译为“专家”或“内行”。Maven是一个跨平台的项目管理工具。主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。仔...
- Java单元测试框架PowerMock学习(java单元测试是什么意思)
-
前言高德的技术大佬在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。”这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现...
- Spring框架基础知识-第六节内容(Spring高级话题)
-
Spring高级话题SpringAware基本概念Spring的依赖注入的最大亮点是你所有的Bean对Spring容器的存在是没有意识的。但是在实际的项目中,你的Bean必须要意识到Spring容器...
- Java单元测试浅析(JUnit+Mockito)
-
作者:京东物流秦彪1.什么是单元测试(1)单元测试环节:测试过程按照阶段划分分为:单元测试、集成测试、系统测试、验收测试等。相关含义如下:1)单元测试:针对计算机程序模块进行输出正确性检验工作...
- 揭秘Java代码背后的质检双侠:JUnit与Mockito!
-
你有没有发现,现在我们用的手机App、逛的网站,甚至各种智能设备,功能越来越复杂,但用起来却越来越顺畅,很少遇到那种崩溃、卡顿的闹心事儿?这背后可不是程序员一拍脑袋写完代码就完事儿了!他们需要一套严谨...
- 单元测试框架哪家强?Junit来帮忙!
-
大家好,在前面的文章中,给大家介绍了以注解和XML的方式分别实现IOC和依赖注入。并且我们定义了一个测试类,通过测试类来获取到了容器中的Bean,具体的测试类定义如下:@Testpublicvoid...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- mfc框架 (52)
- abb框架断路器 (48)
- ui自动化框架 (47)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (65)
- ppt框架 (48)
- 内联框架 (52)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)