动态加载 vs 延迟加载:为什么 demo 里「延迟」看起来没效果?
动态加载 vs 延迟加载为什么 demo 里「延迟」看起来没效果我写了个小 demo 体验 ArkTS 的两种「按需加载」点「动态加载」按钮能明显看到模块是这会儿才被拉进来的可点「延迟加载」按钮……好像没啥不一样而且两种点下去模块都只加载一次。说实话这个「看不出来」的困惑特别好——它恰好说明这俩长得太像了但骨子里是两种东西。这篇就把窗户纸捅破。先把我观察到的三个现象列出来一个个解释动态加载触发事件后才导入而且是await异步导入 ✅你看得很准延迟加载好像没看出「延迟」在哪 两种都只加载一次 ✅一、先退一步三种导入放一起才看得清只盯着「动态」和「延迟」对比容易懵因为它俩都是「不在启动时加载」。得把静态导入也拉进来当参照物。打个比方把「导入一个模块」想象成「让一个员工到岗上班」。静态 import —— 入职即全勤。公司一开门App 冷启动花名册上所有员工全部到岗打卡哪怕今天根本没他的活。人一多开门就慢冷启动变慢。这是我们平时import { x } from ...的默认行为。延迟 import lazy —— 挂名待命。他照常写在花名册里代码顶部照常 import看着和静态一模一样但开门时他不来、不占工位不拖慢启动直到第一次真有活儿找到他他才到岗。关键点来了你给他派活的方式和派给普通员工没有任何区别直接喊一嗓子同步不用等——所以你「感觉不到」他是待命的。这就是你没看出来的原因。动态 import() —— 现叫现到。他压根不在花名册里。你需要时得专门打个电话叫他显式写import()还得等他过来异步await/ Promise。什么时候叫、叫不叫你说了算。一句话先记住静态是「全员到岗」延迟是「挂名待命」动态是「现叫现到」。二、逐个解释你看到的现象现象 1动态加载「点了才导入、还要 await」——对这是它的本色动态 import 是个异步操作返回 Promise// 不在花名册现打电话叫人还得等他来awaitconstns:ESObjectawaitimport(../lazydemo/DynamicFeature);constmsgns.runDynamic();你能清楚看到「这一行执行了模块才被拉进来」是因为加载这个动作是你亲手写出来的import(...)摆在那儿而且它异步、要await存在感很强。现象 2延迟加载「没看出来」——因为我把它也绑在按钮上了我的 demo 里「延迟」按钮点下去会调用// 花名册照常写在文件顶部看着就跟静态一样importlazy{runLazy}from../lazydemo/LazyFeature;// 用的时候跟调用一个普通函数没有任何区别同步没有 awaitconstmsgrunLazy();// ← 第一次执行到这里LazyFeature 才被加载于是它和动态一样「点了才加载」看着就像同一回事。但差别其实藏在两处你只要盯住就能看见写法/调用方式动态要await import()异步、要处理 Promise延迟就一句runLazy()同步和调用普通函数毫无区别。你注意到「动态有 await、延迟没有」——这就是 tell。谁在控制加载动态是你手动编排什么时候 import 你写死的延迟是编译器替你悄悄推迟对你完全透明。换句话说延迟加载的卖点从来不是「什么时候加载」而是「你几乎无感」代码照常写、同步用、不用改成 async它自己把加载推迟到「第一次被用到」。那它的价值怎么才看得出来得拿它和「静态」比冷启动而不是和「动态」比。如果再加一个静态导入的模块App 一启动它的日志就会出现而延迟、动态那两个启动时都静悄悄。这一对比延迟「给冷启动减负」的价值立刻就显形了——而且它的用法还和静态一样省心。现象 3「都只加载一次」——这是模块缓存三种方式的共性员工到岗后就一直在了不会每次派活都重新入职一遍。模块也一样任何模块被求值一次后就被缓存之后再导入/再使用拿的都是缓存。所以「只加载一次」是静态、动态、延迟三者共有的行为不是谁的专属特点。你观察对了但它不是用来区分这两者的点。这也是为什么我 demo 里两个模块的「/ 被加载」日志各自只冒一次之后再点只跑函数。三、并排对比还是用「员工」那套静态 import延迟 import lazy动态 import()比喻入职即全勤挂名待命现叫现到写在哪文件顶部文件顶部看着和静态一样代码中间用到才写何时加载冷启动就加载首次用到那个名字时执行到import()那行用起来同步同步无感和静态一样异步返 Promise要await路径必须是常量必须是常量可以是变量谁控制——编译器自动推迟对你透明你手动编排只加载一次✅✅✅三者都靠模块缓存四、那到底该用哪个按「你想要什么」来选不纠结「这段代码别拖慢启动但我懒得改成异步、用法想照旧」→ 延迟 import lazy。最省心几乎零改动把顶部的import加个lazy就行。绝大多数「单纯想给冷启动减负」的场景用它最合适。「运行期才决定加不加载、加载哪一个路径是变量、或者要等网络/条件满足」→ 动态 import()。最灵活代价是你得处理 Promise、把调用链改成异步。一个经验法则先想清楚是「想省启动时间」还是「想动态控制」。前者优先 lazy改动小后者才上import()。小提醒import lazy只支持具名 / 默认导入import lazy * as ns会编译报错本工程是 API 23直接用即可API 12 上还得在build-profile.json5配compatibleSdkVersionStage: beta3。只有变量形式的动态import(变量)才需要配arkOptions.runtimeOnly。一句话总结动态加载是「现叫现到」——你亲手打电话import()、还得等await存在感强延迟加载是「挂名待命」——写法和静态一模一样、用起来也同步无感它只是悄悄把加载推迟到第一次用到。你「没看出延迟」恰恰是因为它做到了「让你无感」要看出它的价值拿它和「静态导入启动就加载」比而不是和「动态」比。至于「只加载一次」那是模块缓存三种方式都一样。附想让「延迟」的效果一眼可见给 demo 再加一个静态导入的模块做对照即可// 静态App 一启动StaticFeature 顶层日志就会出现在屏幕上import{runStatic}from../lazydemo/StaticFeature;启动时你会看到静态那条日志已经在了而延迟、动态两条都还没出现。这时候「延迟 写法像静态但不在启动时加载」就一目了然了。