• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 运行时动态加载页面提升性能
2
3## 简介
4
5应用在加载页面时,如果引入大量暂不需要加载的模块,会因过多模块导致页面加载缓慢。比如当页面在使用Navigation组件时,主页默认先加载所有页面,此时若包含大量子页面,仅加载主页这一项就需要很长时间,但这些复杂的子页面与主页渲染无关。
6本文推荐使用动态加载解决上述问题,不在应用程序加载时就将所有模块都加载进来,而是按需加载模块,增加应用灵活性,提升应用性能。
7
8## 场景示例
9
10| 主页                                       | 子页面                                        |
11|------------------------------------------|--------------------------------------------|
12| ![主页](./figures/dynamic-import-home.png) | ![子页面](./figures/dynamic-import-pages.png) |
13
14下面示例应用通过Navigation组件常规加载与动态加载的对比,介绍如何在跳转时触发加载方法,实现按需加载子模块。
15
16### 常规加载
17
18开发者使用Navigation组件时,通常会在主页引入各子页面组件,在按钮中添加方法实现跳转。下述代码展示常规加载示例,通过import引入子组件。
19
201. 加载模块引入子页面,Navigation组件使用这些子模块实现跳转子页面功能,但用户进入主页时并不会马上使用到这些模块,存在冗余加载影响性能的可能。
21    ```ts
22    import { pageOne, pageOneData } from './pageOne';
23    import { pageTwo, pagesTwoData } from './pageTwo';
24    ...
25    import router from '@ohos.router';
26    ```
272. 主页通过Navigation组件实现点击Button跳转到不同的子页面。
28    ```ts
29    @Provide('pathInfos') pageInfos: NavPathStack = new NavPathStack();
30
31    @Builder
32    PageMap(name: string) {
33      if (name === 'pageOne') {
34        pageOne(new pagesOneData(name, this.pageInfos));
35      } else if (name === 'pageTwo') {
36        pageTwo(new pagesTwoData(name, this.pageInfos));
37      }
38      ...
39    }
40
41    build() {
42      Navigation(this.pageInfos) {
43        Button('返回', { stateEffect: true, type: ButtonType.Capsule })
44          .onClick(() => {
45             router.back();
46          })
47        Column() {
48          Button('pageOne', { stateEffect: true, type: ButtonType.Capsule })
49            .onClick(() => {
50               this.pageInfos.pushPath({ name: 'pageOne' }); // 将name指定的NavDestination页面信息入栈
51            })
52          Button('pageTwo', { stateEffect: true, type: ButtonType.Capsule })
53            .onClick(() => {
54               this.pageInfos.pushPath({ name: 'pageTwo' });
55            })
56          ...
57        }
58      }.title('主页').navDestination(this.PageMap)
59    }
60    ```
61
62### 动态加载
63
64当子组件较多、较复杂时,由于Navigation组件一次性加载所有模块,使用常规加载会导致主页加载缓慢。为了减少主页渲染时间,可以使用动态加载,在实际页面跳转时再按需动态引入子组件,优化用户的首次加载速度体验。以下是动态加载实现步骤:
65
661. 将需要被动态加载的组件pageOne组件用PageOneLoader函数封装,当PageOneLoader被调用时,会渲染pageOne页面。
67    ```ts
68    import { pageOne } from './pageOne';
69
70    @Builder
71    export function PageOneLoader() {
72      pageOne();
73    }
74    ```
752. 由于在Navigation的PageMap中navDestination无法直接动态加载组件(import是函数,组件中无法引用函数),此处采用声明 @BuilderParam PageOneLoader 函数,在点击时初始化此函数,此时navDestination中可以调用 this.PageOneLoader() 从而加载组件pageOne。
76在主页DynamicHome实现动态加载pageOne的步骤如下:
77   a) 在主页DynamicHome中定义组件加载函数 `@BuilderParam PageOneLoader: () => void` ,用来承接await import异步导入pageOneLoader的结果。
78    ```ts
79    @BuilderParam PageOneLoader: () => void;
80    ```
81   b) 定义异步函数,点击时为PageOneLoader初始化。
82    ```ts
83    async loadPageOne(key: string){
84      if (key === "pageOne") {
85        let PageObj: ESObject = await import("../pages/PageOneLoader");
86        this.PageOneLoader = PageObj.PageOneLoader;
87      }
88    }
89    ```
90   c) 点击按钮触发点击函数,调用loadPageOne,此时真正的初始化 @BuilderParam PageOneLoader ,并使用Navigation加载组件。
91    ```ts
92    private onEntryClick(): void {
93      try {
94        this.loadPageOne('pageOne');
95        this.pageInfos.clear();
96        this.pageInfos.pushPathByName('pageOne', '');
97        logger.info('DynamicImport Success');
98      } catch (error) {
99        logger.info('DynamicImport Fail');
100      }
101    }
102    ```
103   d) 触发PageMap中已经被初始化的PageOneLoader函数,动态加载pageOne组件。
104    ```ts
105    @Builder
106    PageMap(name: string) {
107      if (name === 'pageOne') {
108        this.PageOneLoader();
109      }
110    }
111    ```
112完整的DynamicHome主页代码如下:
113```ts
114import router from '@ohos.router';
115import { logger } from '../../ets/utils/Logger';
116
117@Entry
118@Component
119struct DynamicHome {
120  @Provide('pathInfos') pageInfos: NavPathStack = new NavPathStack();
121  @State active: boolean = false;
122  @BuilderParam PageOneLoader: () => void;
123
124  @Builder
125  PageMap(name: string) {
126    if (name === 'pageOne') {
127      this.PageOneLoader();
128    }
129  }
130
131  build() {
132    Navigation(this.pageInfos) {
133      Column() {
134        Button('返回', { stateEffect: true, type: ButtonType.Capsule })
135          .width('80%')
136          .height(40)
137          .margin(20)
138          .onClick(() => {
139            router.back();
140          })
141        Button('PageOne-动态', { stateEffect: true, type: ButtonType.Capsule })
142          .width('80%')
143          .height(40)
144          .margin(20)
145          .onClick(() => {
146            this.onEntryClick();
147          })
148      }
149    }.title('HOME').navDestination(this.PageMap)
150  }
151
152  async loadPageOne(key: String) {
153    if (key === "pageOne") {
154      let PageObj: ESObject = await import("../pages/PageOneLoader");
155      this.PageOneLoader = PageObj.PageOneLoader;
156    }
157  }
158
159  // 触发动态加载
160  private onEntryClick(): void {
161    try {
162      this.loadPageOne('pageOne');
163      this.pageInfos.clear();
164      this.pageInfos.pushPathByName('pageOne', '');
165      logger.info('DynamicImport Success');
166    } catch (error) {
167      logger.info('DynamicImport Fail');
168    }
169  }
170}
171```
172当子页面逐渐增多时,Navigation组件使用常规加载会因默认加载所有子页面导致性能开销增大,主页内存占用增加,加载时间变长。此时改用动态加载不再提前加载子页面,可以有效地避免这些任务阻塞主线程,从而降低整体资源消耗,提高主页的加载速度。
173