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```