1# 模块加载副作用及优化
2## 概述
3当使用[ArkTS模块化](module-principle.md)时,模块的加载和执行可能会引发**副作用**。副作用指的是模块导入时除了导出功能或对象之外,额外的行为或状态变化,**这些行为可能影响程序的其他部分,并导致产生非预期的顶层代码执行、全局状态变化、原型链修改、导入内容未定义等问题**。
4
5## ArkTS模块化导致副作用的场景及优化方式
6### 模块执行顶层代码
7**副作用产生场景**
8
9模块在被导入时,整个模块文件中的顶层代码会立即执行,而不仅仅是导出的部分。这意味着,即使只想使用模块中的某些导出内容,但是任何在顶层作用域中执行的代码也会被运行,从而产生副作用。
10```typescript
11// module.ets
12console.log("Module loaded!"); // 这段代码在导入时会立即执行,可能会导致副作用。
13export const data = 1;
14
15// main.ets
16import { data } from  './module' // 导入时,module.ets中的console.log会执行,产生输出。
17console.log(data);
18```
19输出内容:
20```typescript
21Module loaded!
221
23```
24**产生的副作用**
25
26即使只需要data,console.log("Module loaded!") 仍会运行,导致开发者可能预期只输出data的值,但却额外输出了“Module loaded!”,**影响输出内容**。
27
28**优化方式**
29
30优化方式1:去除顶层代码,只导出需要的内容,避免不必要的代码执行。
31```typescript
32// module.ets
33export const data = 1;
34
35// main.ets
36import { data } from  './module'
37console.log(data);
38```
39输出内容:
40```typescript
411
42```
43优化方式2:将可能引发副作用的代码放在函数或方法内部,只有在需要时再执行,而不是在模块加载时立即执行。
44```typescript
45// module.ets
46export function initialize() {
47    console.log("Module loaded!");
48}
49export const data = 1;
50
51// main.ets
52import { data } from  './module'
53console.log(data);
54```
55输出内容:
56```typescript
571
58```
59### 修改全局对象
60**副作用产生场景**
61
62顶层代码或导入的模块可能会直接**操作全局变量**,从而改变全局状态,引发副作用。
63```typescript
64// module.ets
65export let data1 = "data from module"
66globalThis.someGlobalVar = 100; // 改变了全局状态
67
68// sideEffectModule.ets
69export let data2 = "data from side effect module"
70globalThis.someGlobalVar = 200; // 也变了全局状态
71
72// moduleUseGlobalVar.ets
73import { data1 } from './module' // 此时可能预期全局变量someGlobalVar的值为100
74export function useGlobalVar() {
75    console.log(data1);
76    console.log(globalThis.someGlobalVar); // 此时由于main.ets中加载了sideEffectModule模块,someGlobalVar的值已经被改为200
77}
78
79// main.ets(执行入口)
80import { data1 } from "./module" // 将全局变量someGlobalVar的值改为100
81import { data2 } from "./sideEffectModule" // 又将全局变量someGlobalVar的值改为200
82import { useGlobalVar } from './moduleUseGlobalVar'
83
84useGlobalVar();
85function maybeNotCalledAtAll() {
86    console.log(data1);
87    console.log(data2);
88}
89```
90输出内容:
91```
92data from module
93200
94```
95**产生的副作用**
96
97模块加载时直接改变全局变量globalThis.someGlobalVar的值,**影响其他使用该变量的模块或代码**。
98
99**优化方式**
100
101将可能引发副作用的代码放在函数或方法内部,只有在需要时再执行,而不是在模块加载时立即执行。
102```typescript
103// module.ets
104export let data1 = "data from module"
105export function changeGlobalVar() {
106    globalThis.someGlobalVar = 100;
107}
108
109// sideEffectModule.ets
110export let data2 = "data from side effect module"
111export function changeGlobalVar() {
112    globalThis.someGlobalVar = 200;
113}
114
115// moduleUseGlobalVar.ets
116import { data1, changeGlobalVar } from './module'
117export function useGlobalVar() {
118    console.log(data1);
119    changeGlobalVar(); // 在需要的时候执行代码,而不是模块加载时执行。
120    console.log(globalThis.someGlobalVar);
121}
122
123// main.ets(执行入口)
124import { data1 } from "./module"
125import { data2 } from "./sideEffectModule"
126import { useGlobalVar } from './moduleUseGlobalVar'
127
128useGlobalVar();
129function maybeNotCalledAtAll() {
130    console.log(data1);
131    console.log(data2);
132}
133```
134输出内容:
135```
136data from module
137100
138```
139### 修改应用级ArkUI组件的状态变量信息
140**副作用产生场景**
141
142顶层代码或导入的模块可能会直接**修改应用级ArkUI组件的状态变量信息**,从而改变全局状态,引发副作用。
143```typescript
144// module.ets
145export let data = "data from module"
146AppStorage.setOrCreate("SomeAppStorageVar", 200); // 修改应用全局的UI状态
147
148// Index.ets
149import { data } from "./module" // 将AppStorage中的SomeAppStorageVar改为200
150
151@Entry
152@Component
153struct Index {
154    // 开发者可能预期该值为100,但是由于module模块导入,该值已经被修改为200,但开发者可能并不知道值已经被修改
155    @StorageLink("SomeAppStorageVar") message: number = 100;
156    build() {
157        Row() {
158            Column() {
159                Text("test" + this.message)
160                    .fontSize(50)
161            }
162            .width("100%")
163        }
164        .height("100%")
165    }
166}
167function maybeNotCalledAtAll() {
168    console.log(data);
169}
170```
171显示内容:
172```
173test200
174```
175**产生的副作用**
176
177模块加载时直接改变AppStorage中SomeAppStorageVar的值,**影响其他使用该变量的模块或代码**。
178
179ArkUI组件的状态变量信息可以通过一些应用级接口修改,详见[ArkUI状态管理接口文档](../quick-start/arkts-state-management-overview.md)。
180
181**优化方式**
182
183将可能引发副作用的代码放在函数或方法内部,只有在需要时再执行,而不是在模块加载时立即执行。
184```typescript
185// module.ets
186export let data = "data from module"
187export function initialize() {
188    AppStorage.setOrCreate("SomeAppStorageVar", 200);
189}
190
191// Index.ets
192import { data } from "./module"
193
194@Entry
195@Component
196struct Index {
197    @StorageLink("SomeAppStorageVar") message: number = 100;
198    build() {
199        Row() {
200            Column() {
201                Text("test" + this.message)
202                    .fontSize(50)
203            }
204            .width("100%")
205        }
206        .height("100%")
207    }
208}
209function maybeNotCalledAtAll() {
210    console.log(data);
211}
212```
213显示内容:
214```
215test100
216```
217### 修改内置全局变量或原型链(ArkTS内禁止修改对象原型与内置方法)
218**副作用产生场景**
219
220某些第三方库或框架可能会修改内置的全局对象或原型链,以便在较旧的浏览器或运行环境中支持现代的JavaScript特性。这可能会影响其他代码的运行。
221```typescript
222// modifyPrototype.ts
223export let data = "data from modifyPrototype"
224Array.prototype.includes = function (value) {
225    return this.indexOf(value) !== -1;
226};
227
228// main.ets
229import { data } from "./modifyPrototype" // 此时修改了Array的原型链
230let arr = [1, 2, 3, 4];
231console.log(arr.includes(1)); // 此时调用的是modifyPrototype.ts中的Array.prototype.includes方法
232function maybeNotCalledAtAll() {
233    console.log(data);
234}
235```
236**产生的副作用**
237
238修改内置的全局对象或原型链,影响其他代码运行。
239
240**优化方式**
241
242导入可能会修改内置的全局对象或原型链的第三方库时,确认该第三方库的行为是符合预期的。
243### 循环依赖
244
245**副作用产生场景**
246
247ArkTS模块化支持循环依赖,即模块A依赖模块B,同时模块B又依赖模块A。在这种情况下,某些导入的模块可能尚未完全加载,从而导致部分代码在执行时行为异常,产生意外的副作用。
248```typescript
249// a.ets
250import { b } from "./b"
251console.log('Module A: ', b);
252export const a = 'A';
253
254// b.ets
255import { a } from "./a"
256console.log('Module B: ', a);
257export const b = 'B';
258```
259输出内容:
260```
261Error message: a is not initialized
262Stacktrace:
263    at func_main_0 (b.ets:2:27)
264```
265**产生的副作用**
266
267由于模块间相互依赖,模块的执行顺序可能导致导出的内容为空或未定义,影响代码的逻辑流。
268
269**优化方式**
270
271尽量避免模块间的循环依赖,确保模块的加载顺序是明确和可控的,以避免产生意外的副作用。[@security/no-cycle循环依赖检查工具](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide_no-cycle-V5) 可以辅助检查循环依赖。
272### 延迟加载(lazy import)改变模块执行顺序,可能导致预期的全局变量未定义
273**副作用产生场景**
274
275[延迟加载](arkts-lazy-import.md)特性可使待加载模块在冷启动阶段不被加载,直至应用程序实际运行过程中需要用到这些模块时,才按需同步加载相关模块,从而缩短应用冷启动耗时。但这也同时会改变模块的执行顺序。
276```typescript
277// module.ets
278export let data = "data from module"
279globalThis.someGlobalVar = 100;
280
281// moduleUseGlobalVar.ets
282import lazy { data } from "./module"
283console.log(globalThis.someGlobalVar); // 此时由于lazy特性,module模块还未执行,someGlobalVar的值为undefined
284console.log(data); // 使用到module模块的变量,此时module模块执行,someGlobalVar的值变为100
285```
286输出内容:
287```
288undefined
289data from module
290```
291**产生的副作用**
292
293由于使用到延迟加载(lazy import)特性,会导致模块变量在使用到时再执行对应的模块,模块中的一些全局变量修改行为也会延迟,可能会导致运行结果不符合预期。
294
295**优化方式**
296
297将可能引发副作用的代码放在函数或方法内部,只有在需要时再执行,而不是在模块加载时立即执行。
298```typescript
299// module.ets
300export let data = "data from module"
301export function initialize() {
302    globalThis.someGlobalVar = 100; // 延迟到函数调用时执行
303}
304
305// moduleUseGlobalVar.ets
306import lazy { data, initialize } from "./module"
307initialize(); // 执行初始化函数,初始化someGlobalVar
308console.log(globalThis.someGlobalVar); // 此时someGlobalVar一定为预期的值
309console.log(data);
310```
311输出内容:
312```
313100
314data from module
315```