在开发移动应用时,您应该始终注意性能并进行优化。在这篇文章中,我将向您介绍一种非常有效的模式,用于优化使用 Angular 时应用程序的加载时间。这就是“Angular 延迟加载”。
您现在可以观看 2018 年 6 月在 YouTube 上发布的关于 Angular/Vue.js 代码共享网络研讨会 的完整视频!
使用延迟加载,我们可以将应用程序拆分为功能模块,并按需加载它们。主要优点是,我们只加载用户在启动屏幕上期望看到的内容。其他模块只有在用户导航到其路由时才会加载。
查看 Victor Savkin 的这篇博文,获取有关延迟加载的更深入信息。
Angular 内置的模块加载器使用 SystemJS。但是,在开发 NativeScript 应用时,我们不需要设置 SystemJS 来创建延迟加载的模块。我们可以使用 NativeScript 模块加载器(随 NativeScript Angular 分发,推荐的方式)、编写自己的模块加载器,或者完全不使用模块加载器进行延迟加载。
在接下来的部分中,我们将使用简单的 lazyNinjas 应用,它有两个模块 - HomeModule(未延迟加载)和 NinjasModule(延迟加载)。我们将在接下来的部分中介绍启用延迟加载的不同方法。花点时间熟悉一下忍者 - 从 github 下载应用程序并运行它。
我们可以通过在根 NgModule 中提供另一个模块加载器来替换默认的应用程序模块加载器。对于 NativeScript 应用程序,我们建议使用 NSModuleFactoryLoader
现在,让我们看一下路由器配置
`routes` 数组是我们应用程序的实际路由器配置。
首先,我们使用 ES2015 展开运算符('...')添加 HomeModule 路由。
...homeRoutes,
然后,我们注册延迟加载的模块。当用户导航到 '/ninjas' 时,Angular 将加载位于 './ninjas/ninjas.module' 中的 NinjasModule。
{ path: "ninjas", loadChildren: "./ninjas/ninjas.module#NinjasModule", }
在文件 "./ninjas/ninjas.module.ts" 中,我们定义了 NinjasModule 类,以及它的路由。我们使用 `NativeScriptRouterModule` 的 `forChild` 方法导入路由。以下是示例:
就这样!我们建议在使用 NativeScript 应用程序时使用这种延迟加载方法。
如果我们想要嵌套延迟加载模块,我们应该在所有包含延迟加载路由的 NgModule 中提供模块加载器。作为参考,请查看 lazyNinjas 存储库中的 nested-lazy-modules 分支。
在接下来的部分中,我们将介绍两种在更复杂场景中可以使用的方法。
还记得路由器配置中的 loadChildren 方法吗?不记得了?这里再次展示:
const routes = [ ...homeRoutes, { path: "ninjas", loadChildren: "./ninjas/ninjas.module#NinjasModule" } ];
我们使用它来指定 NinjasModule 的路径。在内部,模块加载器会获取该字符串并使用它从文件中加载模块。但是,我们可以传递回调函数而不是字符串。例如:
loadChildren: () => require("./ninjas/ninjas.module.js")["NinjasModule"]
让我们一步步看一下发生了什么。
首先,在 "./ninjas/ninjas.module.ts" 文件中,我们导出了 NgModule。这被转译成以下 JavaScript 代码:
这使得我们可以像在传递给 `loadChildren` 属性的回调函数中一样,获取模块对象(exports)并查询 "NinjasModule"。
loadChildren: () => require("./ninjas/ninjas.module.js")["NinjasModule"]
我们也可以省略文件的扩展名,并将上面的代码写成:
loadChildren: () => require("./ninjas/ninjas.module")["NinjasModule"]
您可以在 callback-loading 分支中找到应用程序的这个版本。
与其为每个 `loadChildren` 属性传递回调函数,不如将加载逻辑提取到模块加载器中。这正是我们建议使用 NativeScript 模块加载器的原因。但是,我们也可以创建自己的加载器并使用它。
让我们看一下在我们的简单应用程序中使用的 NinjaModuleLoader
加载器实现了 `NgModuleFactoryLoader`,它只有一个方法 - `load`,它带有一个参数 - 路径,我们将将其传递给 `loadChildren` 属性。我们将采用内置模块加载器使用的相同语法。如 Angular 文档中所述,“延迟加载模块的位置是一个字符串,而不是一个类型... 该字符串标识模块文件和模块类,后者与前者用 # 分隔。”
我们应该更改路由器配置中的所有 `loadChildren` 属性。幸运的是,我们只有一个
loadChildren: "./ninjas/ninjas.module#NinjasModule"
NinjasModuleLoader 的私有 `splitPath` 方法用于提取模块文件位置和导出的模块名称。然后,我们可以像在回调函数中一样简单地获取模块
let loadedModule = require(modulePath)[exportName];
提取加载逻辑允许我们检查模块是否存在,如果不存在,则抛出有意义的错误消息。
if (!loadedModule) {
throw new Error(`Cannot find "${exportName}" in "${modulePath}"`);
}
return this.compiler.compileModuleAsync(loadedModule);
最后,我们需要在根模块中注册我们的模块加载器**。**
您可以在 custom-module-loader 分支中找到应用程序的最终版本。
* 请注意,您可以为每个 NgModule 使用不同的模块加载器!
对于具有延迟加载模块的真实 NativeScript 应用程序,您可以查看我们的 SDK 示例。它有超过 100 个不同的组件,每个组件都有自己的路由,以及大约 15 个功能模块。下表展示了有无延迟加载的启动时间。
平台 |
设备 |
无延迟加载 |
有延迟加载 |
Android |
Nexus 5 |
~13 秒 |
~4 秒 |
iOS |
iPhone 5s |
~12 秒 |
~3 秒 |
对于我们的 2.5 版本,我们正在努力启用 提前编译 和 Webpack 2,并使用 树摇 和 混淆,这将显著提高加载时间。请在接下来的几周内关注更新。