介绍热加载
React Native的目标是为您提供最佳的开发人员体验。其中很大一部分是您保存文件并能够看到更改所需的时间。我们的目标是让这个反馈循环不到1秒钟,即使您的应用程序增长。
我们通过三个主要功能接近这个理想:
- 使用JavaScript作为语言没有长的编译周期时间。
- 实施一个名为Packager的工具,将工具将es6 / flow / jsx文件转换为VM∂可以理解的普通JavaScript。它被设计为将内部中间状态保留在内存中以实现快速增量更改并使用多个内核的服务器。
- 构建一个名为Live Reload的功能,可在保存时重新加载应用程序。
在这一点上,开发人员的瓶颈不再是重新加载应用程序所需的时间,而是失去了应用程序的状态。一个常见的情况是处理一个远离启动屏幕的多个屏幕的功能。每次重新加载时,您都必须一次又一次地点击相同的路径,以恢复您的功能,使该周期长达几秒钟。
热加载
热加载的想法是保持应用运行,并注入您在运行时编辑的文件的新版本。这样,如果您调整UI,您不会失去任何特别有用的状态。
简单实现
现在我们已经看到了为什么我们想要它和如何使用它,有趣的部分开始:它是如何实际工作的。
热加载建立在热模块更换或HMR 功能之上。它是Webpack首次引入,我们在React Native Packager内部实现了它。HMR使Packager监视文件更改,并将HMR更新发送到应用程序中包含的薄型HMR运行时间。
简而言之,HMR更新包含更改的JS模块的新代码。当运行时接收到它们时,它将使用新的代码替换旧模块的代码:
HMR更新包含的不仅仅是我们要更改的模块代码,因为替换它,运行时不足以接收更改。问题是模块系统可能已经缓存了我们要更新的模块的导出。例如,说你有一个由这两个模块组成的应用程序:
// 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
打印出提供的消息,包括模块提供的当前日期time
。
当应用程序捆绑时,React Native使用该__d
功能在模块系统上注册每个模块。对于这个应用程序,在许多__d
定义中,将有一个log
:
__d ('log' , function () { ... // module's code } );
这种调用将每个模块的代码包装成一个匿名函数,我们通常将其称为工厂函数。模块系统运行时跟踪每个模块的工厂功能,无论是否已经执行,以及执行(导出)的结果。当需要模块时,模块系统首次提供已缓存的导出或执行模块的出厂功能,并保存结果。
所以说你开始你的应用程序并要求log
。在这一点上,也log
没有time
执行任何工厂功能,因此没有缓存出口。然后,用户修改time
以返回日期MM/DD
:
// time.js
function bar() {
var date = new Date();
return `${date.getMonth() + 1}/${date.getDate()}`;
}
module.exports = bar;
Packager会将时间的新代码发送到运行时(步骤1),并且当log
最终需要导出的函数被执行时,它将通过time
更改执行此操作(步骤2):
现在说log
要求的代码time
作为顶级要求:
const time = require('./time'); // top level require
// log.js
function log(message) {
console.log(`[${time()}] ${message}`);
}
module.exports = log;
什么时候log
需要,运行时将缓存其导出和time
一个。(步骤1)。然后,time
修改后,HMR进程在替换time
代码后不能简单完成。如果是这样,当log
被执行时,它将使用time
(旧代码)的缓存副本执行。
为了log
获取time
更改,我们需要清除缓存的导出,因为它所依赖的模块之一被热交换(步骤3)。最后,当log
再次需要时,其工厂功能将被执行,要求time
并获得新的代码。
HMR API
React Native中的HMR通过引入hot
对象来扩展模块系统。这个API是基于Webpack的。该hot
对象暴露了一个调用的函数,accept
它允许您定义当模块需要热插拔时将执行的回调。例如,如果我们改变time
代码如下,每次我们节省时间,我们将在控制台中看到“更改时间”
// time.js
function time() {
... // new code
}
module.hot.accept(() => {
console.log('time changed');
});
module.exports = time;
请注意,只有在极少数情况下,您需要手动使用此API。对于最常见的用例,Hot Reloading应该开箱即用。
HMR运行时
正如我们以前看到的,有时仅接受HMR更新是不够的,因为使用热插拔的模块可能已被执行,并且其导入已被缓存。例如,假设电影应用程序示例的依赖关系树具有MovieRouter
依赖于MovieSearch
和MovieScreen
视图的顶级,这取决于前面示例中的模块log
和time
模块:
如果用户访问电影的搜索视图而不是另一个,除了所有模块都MovieScreen
将缓存导出。如果对模块进行了更改time
,运行时将必须清除其导出log
以接收time
更改。该过程不会在那里完成:运行时将重复此过程,直到所有的父母都被接受。所以,它会抓住依赖的模块log
并尝试接受它们。因为MovieScreen
它可以保释,因为它还没有被要求。因此MovieSearch
,它必须清除其出口并递归处理其父母。最后,它会做同样的事情MovieRouter
,完成它,因为没有模块依赖它。
为了运行依赖关系树,运行时间在HMR更新中从Packager接收到逆依赖关系树。对于这个例子,运行时将会收到像这样一个JSON对象:
{
modules: [
{
name: 'time',
code: /* time's new code */
}
],
inverseDependencies: {
MovieRouter: [],
MovieScreen: ['MovieRouter'],
MovieSearch: ['MovieRouter'],
log: ['MovieScreen', 'MovieSearch'],
time: ['log'],
}
}
反应组件
反应组件有点难以使用热加载。问题是,我们不能简单地用旧的代码替换旧的代码,因为我们会松开组件的状态。对于阵营的Web应用程序,达恩·阿勃拉莫夫实施了巴贝尔变换使用的WebPack的HMR API来解决这个问题。简而言之,他的解决方案通过在转换时间为每个单一的React组件创建一个代理。代理保存组件的状态,并将生命周期方法委托给实际组件,这些组件是我们热加载的组件:
除了创建代理组件之外,变换还accept
使用一段代码来定义函数,以强制React重新渲染组件。这样,我们可以重新加载渲染代码,而不会丢失任何应用程序的状态。
React Native附带的默认变压器使用babel-preset-react-native
,它被配置为使用react-transform
与使用Webpack的React Web项目相同的方式。
Redux商店
要在Redux商店上启用热重新加载,您只需要使用HMR API,就像使用Webpack的Web项目所做的一样:
// configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from '../reducers';
export default function configureStore(initialState) {
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk),
);
if (module.hot) {
module.hot.accept(() => {
const nextRootReducer = require('../reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
};
当您更改reducer时,接受该reducer的代码将被发送到客户端。然后,客户端会意识到,reducer不知道如何接受自己,所以它会查找引用它的所有模块并尝试接受它们。最终,流程将到达单个存储区,该configureStore
模块将接受HMR更新。
结论
如果您有兴趣帮助热加载变得更好,我鼓励您阅读Dan Abramov在未来的热加载中发表的文章,并做出贡献。例如,Johny Days将使其与多个连接的客户端协同工作。我们依靠你们来维护和改进这个功能。
使用React Native,我们有机会重新思考我们构建应用程序的方式,以使其成为一个伟大的开发人员体验。热加载只是一个难题,还有什么其他疯狂的黑客可以做得更好?