平时使用WebStorm或VSCode对RN工程中的文件修改后,在键盘上按一下快捷cmd+s进行文件保存,此时当前调试的RN页面就会自动进行刷新,这是RN开发相比于原生开发一个很大的优点:热更新。 那么,从按一下快捷cmd+s到RN页面展示出最新的JS页面,这个过程是怎样发生的呢?下面根据时间顺序来梳理一下。 这里约定后面说的原生部分是指iOS端,ReactNative源码分析指的是iOS端集成的RN框架分析。 原生APP中RN页面的热更新简要流程 React Native应用包含两部分:原生代码和JavaScript代码。JavaScript和原生代码通信的桥梁是Bridge,而Bridge的实现又依赖于JSThread的runloop。 当JS有消息要传递给原生时,它会把消息封装成事件放入到JSThread的runloop的事件队列中,而JSThread的runloop会监听消息队列中的事件,一旦有事件需要处理,就会将其交给Bridge处理,从而实现JavaScript和原生代码的相互调用和数据传输。 基于JavaScript和原生代码的消息传递机制,RN热更新步骤如下: 1.在iOS原生中的RN页面触发热更新时,首先是JS环境中的websocket收到了Metro服务器的通知,在这个通知中包含了需要更新的JS bundle的URL地址,然后js将这个通知事件放到了JSThread的runloop中传到了RCTCxxBridge。 2.RCTCxxBridge收到热更新事件后,调用JavaScriptCore框架中的方法来执行一个JavaScript脚本,这个脚本会告诉JavaScript环境去下载新的JS bundle并执行它。事件又回到了JS。 3.JavaScript环境调用下载命令向远程服务器请求新的JS bundle,事件又被转回到了原生侧。 4.原生侧下载最新的bundle.js,下载完成后调用加载,执行js方法。 5.重新设置RN页面的根组件。 热更新触发条件 React Native在调试模式下有两种热更新方式:Hot Reloading和Live Reload。Hot Reloading可以实现代码的增量更新,而Live Reload技术则是全量更新。 下面以在index.js中新增一个组件注册语句为例。 在RN项目中index.js是ReactNative项目的入口文件,RN启动时首先会执行这个文件,把组件注册到AppRegistry中,这里在index.js的文件底部顺序添加一个组件注册,然后按快捷键cmd+s保存修改
AppRegistry.registerComponent('FlatListDemo', () => FlatListDemo);这时本地的Packager服务会监听本地文件系统的变化,当有文件修改并保存时,Packager会运行RN命令行工具,自动生成一个新的bundle文件
react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ios/main.jsbundle --assets-dest ios然后将生成的bundle文件上传至服务器,接着Metro 服务器通过 WebSocket 链接发送消息给APP应用程序。
触发热更新
在APP中RN框架启动时,会在APP的JS运行上下文中创建一个websocket链接并与js服务器建立连接,它用于监听Metro服务器发来的bundle.js更新通知。 当Packger打了新的bundle.js并上传到js服务器后,Metor服务器就会向与它链接的websocket发送更新通知,js引擎收到服务器的通知后,就会做后续的事件处理。 具体js建立websocket的代码如下: 在iOS端ReactNative框架中,RCTCxxBridge文件中的+ (void)runRunLoop方法内负责创建并运行JS引擎对应的jsThread的runloop。它在APP启动时被创建,用来处理js引擎的任务和事件,并保证了_jsThread的常驻,不被销毁。 runloop被注册时机在RN框架启动时: js引擎收到服务器的通知后,被包成一个原生runloop的事件源放入到事件队列中,然后jsThread的runloop开始处理事件。 然后把事件加入到RCTMessageThread中进行异步处理 RCTCxxBridge调用js脚本,让js处理与最新bundle.js下载相关的事件。把事件传入到js事件队列中 js引擎处理完JS层面的事件后,将事件转回给原生,让原生代码执行实际的下载工作 原生侧的RCTDevSettings模块的reloadWithReason:方法进行处理。调用ReloadCommand监听的触发器,进行触发reloadCommand命令 RCTReloadCommand发送didReceiveReloadCommand收到下载指令 接着调用RCTBridge中的didReceiveReloadCommand方法,RCTBridge中销毁之前的js缓存,调用setUp进行js环境重置,js资源下载 js资源下载完成后,执行js代码 js资源reload完成后,调用js引擎,展示目标组件 当js文件加载成功后,APP会重新创建一个RCTRootContentView, 并将旧的移除,把新的添加上去。新的RCTRootContentView.reactTag使用规则递增- (void)bundleFinishedLoading:(RCTBridge *)bridge { RCTAssert(bridge != nil, @"Bridge cannot be nil"); if (!bridge.valid) { return; } [_contentView removeFromSuperview]; _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds bridge:bridge reactTag:self.reactTag sizeFlexiblity:_sizeFlexibility]; [self runApplication:bridge]; _contentView.passThroughTouches = _passThroughTouches; [self insertSubview:_contentView atIndex:0]; if (_sizeFlexibility == RCTRootViewSizeFlexibilityNone) { self.intrinsicContentSize = self.bounds.size; } } - (void)runApplication:(RCTBridge *)bridge { NSString *moduleName = _moduleName ?: @""; NSDictionary *appParameters = @{ @"rootTag" : _contentView.reactTag, @"initialProps" : _appProperties ?: @{}, }; RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters); [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, appParameters ] completion:NULL]; }
至此,APP中RN页面的热更新主要流程结束。
标签:原生,APP,bundle,js,源码,RN,JS From: https://www.cnblogs.com/zhou--fei/p/17410767.html