美文网首页
React Native的热更新

React Native的热更新

作者: sea_biscute | 来源:发表于2018-12-17 16:06 被阅读30次

Hot Reloading

React Native(下文简称RN)致力于提供最好的开发体验,亮点之一就是极大缩短了修改文件到页面刷新的时间。在文件修改之后,页面可以直接获取到这一变动并更新逻辑,这一技术被称为Hot Reloading
RN这一技术的实现依赖以下三种特性:

  • 使用JavaScript作为开发语言,规避了长时间编译的问题。
  • 使用Packager的工具将es6/flow/jsx文件转为Virtual Dom可以理解的普遍JS文件。Packager以服务器的形式将中间状态保存在内存中,这一处理使得对快速更新变动提供了强效支持,并且使用多内核处理。
  • 使用一个称为Live Reload的特性在项目保存后刷新。

通过以上特性,开发瓶颈由编译时间变为如何保持APP的state。

Hot Reloading实现

Hot Reloading的设计是在文件发生变化时,实时去刷新应用的状态。Hot Reloading是在Hot Module Replacement特性的基础上实现的,简称HMR(热组件替换)。这个特性由Webpack首次提出,现在应用在RN Packager。

HMR包含了JS组件改变后的最新代码,在HMR Runtime收到时,会将旧的代码替换为新的:


HMR structure

下面通过代码示例进行讲解:

// log.js
function log(message) {
  const time = require('./time');
  console.log(`[${time()}] ${message}`);
}

module.exports = log;
// time.js
function time() {
  return new Date().getTime();
}

module.exports = time;

可以看到在log.js内依赖了time。
在应用打包时,RN会在组件系统内通过__d方法注册每个组件。对于以上示例来说,在众多的__d定义中,log的定义如下:

__d('log', function() {
  ... // module's code
});

RN将每个组件的代码通过匿名函数的方式封装,类似于我们知道的工厂方法。组件系统的运行时会追踪每个组件的工厂方法。
组件在被获取后会进行缓存,缓存与否的处理方式是不一样的。
未缓存:在time的代码发生改变时,Packager会将time的最新代码发送给runtime。

no cacke

存在缓存:先清除缓存 -> 替换time -> 在log使用time时重新建立缓存

has been required

HMR API

RN中通过引入hot对象的方式扩展了组件系统。hot对象提供了accept方法用于接受组件被修改后的回调方法。
下面这个示例,是说明了HMR中accept的用法,并没有使用React Hot Loader,没有React Transform等其他RN Hot Reloading的特性。HMR并不会改变React组件的语法,HMR只是一个用于处理几个步骤的优秀框架:获取更新,将更新更新的模块注入到脚本,调用回调。


var React = require('react')
var ReactDOM = require('react-dom')

// Render the root component normally
var rootEl = document.getElementById('root')
ReactDOM.render(<App />, rootEl)

// Are we in development mode?
if (module.hot) {
  // Whenever a new version of App.js is available
  module.hot.accept('./App', function () {
    // Require the new version and render it instead
    var NextApp = require('./App')
    ReactDOM.render(<NextApp />, rootEl)
  })
}

WebPack设计HMR的内部细节

上面我们说过,React Native的Hot Reloading是在HMR的基础上实现的,而HMR是WebPack的重要特性,我们下面分析下,HRM的实现细节。

对于应用来说更新步骤如下:

  1. 应用使用HRM runtime去检测更新。
  2. runtime异步下载更新,通知应用。
  3. 告知runtime去应用更新
  4. 同步去应用更新

对于编译器的更新步骤如下:

  1. 更新manifest
  2. 更新chunks,chunks是WebPack用于代码分离的工具之一。

manifest内包含新的编译哈希和已更新的chunks列表。

对于Runtime的更新步骤:
这里我们先说一下Runtime:对于manifest来讲,runtime主要是指在浏览器运行时,webpack 用来连接模块化的应用程序的所有代码。runtime 包含在模块交互时连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。
对于组件系统运行时,runtime提供了两个方法:checkapply
check会发起HTTP请求来更新manifest,如果请求失败,则更新失败。请求成功后会对比新的chunks和已经加载的chunks列表。当所有更新的chunks下载完成并且可以应用时,runtime会切换到ready状态。

apply方法会将所有更新的组件标记为不可用。对于不可用组件,需要handler去处理,如果没有处理,会逐级向上寻找,知道找到handler或者程序入口,如果直至入口都没有处理,就会退出程序。

HMR Runtime

如果需要更新的组件,已经被缓存了,就不能单纯的只是完成替换,需要先接触缓存的绑定关系。清除依赖关系的方式是递归进行的。


clear recursively

对于已经缓存的组件,会查找依赖关系,并逐级解除。例如上图,log的上级有MovieScreen和MovieSearch,MovieScreen没有缓存log,所以递归结束。MovieSearch依赖log,而MovieRouter依赖MovieSearch,他们之间的缓存也需要解除。
为了遍历依赖树,会创建一个逆序依赖文件,如下:

{
  modules: [
    {
      name: 'time',
      code: /* time's new code */
    }
  ],
  inverseDependencies: {
    MovieRouter: [],
    MovieScreen: ['MovieRouter'],
    MovieSearch: ['MovieRouter'],
    log: ['MovieScreen', 'MovieSearch'],
    time: ['log'],
  }
}

React 组件

React组件使用Hot Reloading机制要更复杂些,因为不能单纯的替换代码,这样会导致DOM和组件的状态丢失
因为模块被重新计算了,内部组件的ID和过去的不同,对于React而言,你想要重新渲染一个全心的组件,所以React会卸载过去组件。所以,如上所说,React会摧毁组件的DOM和本地state。
解决这种问题有几种方法:

将state存于外部

类似于Redux这种数据流管理框架,每个组件并不管理自己的状态,而是整个APP公用一个state,每个组件获取这个state内的数据,这样在更新组件时,就不需要担心组件更新的问题。

保存DOM和本地state

为了解决DOM和本地状态被销毁的问题,有两种不同的做法:

  1. 找到一种方法,将React实例从DOM和状态中分离出来,只更新这个实例,完成后将其“粘回”DOM和state。
  2. 使用代理组件类型。这样对于React来说,类型没有改变,不需要重新去卸载和装载,内部的实际实现,会根据热更新发生变化。

失败方案:React实例从DOM和状态中分离
第一种方法听上去更好,但是目前React并没有提供可以分离/聚合React实例和DOM、还有运行的生命周期钩子。哪怕我们可以使用React的私有API,这个方案也未必可行。
例如React组件可能会订阅一些生命周期方法(componentDidMount等),即使我们可以在不摧毁DOM和状态的情况下静默替换旧的实例,但是因为过去的实例没有取消对生命周期的订阅,新的实例也无法订阅成功。

成功方案:Proxy Component代理组件
Proxy Component就是使用在React Hot Loader和React Transform中的方案。这种方案会改变代码的语法,但是目前在React中的应用还算成功。
Proxy就是一个类,内部封装了关于显示和状态的实现。
React Hot Loader将通过module.exports内的React组件通过代理进行封装,输出封装后的代理类。
可以这么理解:当调用<App>render<NavBar>时,实际是在render<NavbarProxy>。

转换机制:在转换时为每个React组件创建代理,代理在组件真正的生命周期中持有它们的状态和其他代理方法。

React Component hot reload by proxy
除了创建代理组件,转换还定义了accept方法,用于对组件进行强制刷新。这样组件的热更新就不会丢失任何状态了。

参考资料

相关文章

网友评论

      本文标题:React Native的热更新

      本文链接:https://www.haomeiwen.com/subject/mjiwhqtx.html