热重载功能正式发布
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
React Native 的核心目标是提供最佳的开发者体验。其中关键环节是从保存文件到看到变更所需的时间。我们的目标是将这个反馈循环压缩到 1 秒以内,即使应用规模持续增长。
我们通过三大特性逼近这个理想目标:
-
采用 JavaScript 语言,避免冗长的编译周期
-
实现名为 Packager 的工具,将 ES6/Flow/JSX 文件转换为虚拟机可理解的常规 JavaScript。该工具设计为服务架构,在内存中保存中间状态以实现快速增量更新,并充分利用多核性能
-
构建 Live Reload 功能,实现保存时自动重载应用
至此,开发者的瓶颈不再是重载应用所需时间,而是应用状态的丢失。典型场景是:当你在距离启动屏多个页面之外的模块上工作时,每次重载都必须重复点击相同路径才能返回工作区,导致开发循环延长至数秒
热重载
热重载的核心原理是保持应用运行状态,在运行时注入已编辑文件的新版本。这样就不会丢失任何状态,特别适合调整 UI 元素
百闻不如一见,请对比 Live Reload(现有)与 Hot Reload(全新)的实际效果:
仔细观察会发现:系统能从红屏错误中自动恢复,还能直接导入新增模块而无需完全重载
重要提示: 由于 JavaScript 是强状态语言,热重载无法完美实现。实践中我们发现当前方案能覆盖大多数常规场景,若出现异常随时可通过完全重载恢复
热重载功能自 0.22 版本起可用,启用方式:
-
打开开发者菜单
-
点击"启用热重载"
实现原理简析
了解其价值和使用方法后,现在进入精彩环节:运作原理揭秘
热重载基于 模块热替换(HMR)功能构建。该技术由 webpack 首创,我们在 React Native Packager 中实现了它。HMR 让 Packager 监听文件变更,并向应用内嵌的轻量 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() {
const date = new Date();
return `${date.getMonth() + 1}/${date.getDate()}`;
}
module.exports = bar;
Packager 会将 time 的新代码发送至运行时(步骤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 模块,热更新流程不能仅替换完 time 的代码就结束。否则当 log 执行时,它仍会使用缓存的旧版 time 代码。
为了让 log 获取 time 的变更,我们需要清除其缓存的导出值(步骤3)。最后当 log 再次被引入时,其工厂函数将重新执行,此时会引入新版 time 代码。

HMR API
React Native 的 HMR 通过引入 hot 对象扩展了模块系统。该 API 基于 webpack 的实现。hot 对象暴露的 accept 函数允许您定义回调,在模块需要热替换时执行。例如,若将 time 代码修改如下,每次保存时控制台将显示 "time changed":
// time.js
function time() {
... // new code
}
module.hot.accept(() => {
console.log('time changed');
});
module.exports = time;
请注意,仅在极少数情况下需要手动使用此 API。热重载功能在大多数常见场景中均可开箱即用。
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'],
}
}
React 组件
要让 React 组件实现热重载会稍显棘手。问题在于如果直接替换新旧代码,我们会丢失组件状态。针对 React Web 应用,Dan Abramov 开发了一套 Babel 转换器,利用 webpack 的 HMR API 解决了这个问题。简言之,他的方案通过在转换阶段为每个 React 组件创建代理来实现。这些代理组件负责保存状态,并将生命周期方法委托给实际组件(即热重载的目标组件):
除了创建代理组件,该转换器还定义了包含强制 React 重新渲染组件的 accept 函数。这样我们就能在不丢失应用状态的前提下热更新渲染代码。
React Native 自带的默认转换器使用 babel-preset-react-native,其配置方式与你在基于 webpack 的 React Web 项目中使用 react-transform 完全一致。
Redux 状态管理
要为 Redux 启用热重载,操作方式与基于 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 无法自我更新,于是查找所有引用它的模块并尝试更新。最终流程会到达唯一的 store —— 即 configureStore 模块,它将接受 HMR 更新。
结语
如果你有兴趣帮助改进热重载功能,推荐阅读 Dan Abramov 关于热重载未来的文章并参与贡献。例如 Johny Days 正在实现多客户端同步热重载。这项功能的维护与优化需要大家共同参与。
通过 React Native,我们得以重新思考应用构建方式以优化开发体验。热重载只是其中一环,还能通过哪些创新方案让体验更上一层楼?