静态导入 vs 动态导入 详解
环境:Vite + Vue 2 + Rollup
时间:2026-07-10
一、基本写法
静态导入(Static Import)
js
import Foo from './Foo.vue'
// 或具名导入
import { Button, Input } from 'element-ui'
- 编译时执行,在模块初始化前就完成
- 只能放在文件顶层,不能放在条件或函数中
动态导入(Dynamic Import)
js
const Foo = () => import('./Foo.vue')
// 带路径变量的动态导入
const compName = 'TableList'
const Comp = () => import(`@/components/${compName}/index.vue`)
- 运行时才执行,返回 Promise
- 可以放在任意位置,可条件加载
二、打包产物差异
场景 1:同一组件被多处引用
假设三个页面都用到了 BigTable.vue(200KB)。
静态导入方式:
js
// ─── PageA.vue ───
import BigTable from '@/components/BigTable.vue'
// ─── PageB.vue ───
import BigTable from '@/components/BigTable.vue'
// ─── PageC.vue ───
import BigTable from '@/components/BigTable.vue'
打包结果:
PageA.\[hash].js ← 内含 BigTable 代码(200KB)
PageB.\[hash].js ← 内含 BigTable 代码(200KB)
PageC.\[hash].js ← 内含 BigTable 代码(200KB)
\_\_\_\_\_
合计多出 400KB 重复体积
动态导入方式:
js
// ─── PageA.vue ───
components: { BigTable: () => import('@/components/BigTable.vue') }
// ─── PageB.vue ───
components: { BigTable: () => import('@/components/BigTable.vue') }
// ─── PageC.vue ───
components: { BigTable: () => import('@/components/BigTable.vue') }
打包结果:
BigTable.\[hash].js ← 唯一 1 份(200KB)
PageA.\[hash].js ← 不含 BigTable
PageB.\[hash].js ← 不含 BigTable
PageC.\[hash].js ← 不含 BigTable
**结论:同一组件被 N 处引用,静态导入打包 N 份,动态导入打包 1 份。**
场景 2:已被全局静态导入过
js
// ─── global.js ───
import dialogAddEdit from '@/components/form/addEdit/index.vue'
Vue.component('DialogAddEdit', dialogAddEdit)
此时 dialogAddEdit 已被打包进主入口 chunk(app.\[hash].js)。
其他组件中再写动态导入:
js
// ─── DialogFieldPower/index.vue ───
components: {
// 动态导入------但组件已存在于主 chunk
dialogAddEdit: () => import('@/components/form/addEdit')
}
打包结果:
app.\[hash].js ← 已有 dialogAddEdit 代码
// 上面的动态导入不会产生独立 chunk
// 编译后等价于:
// dialogAddEdit: () => Promise.resolve(app chunk 中已存在的模块)
**结论:组件已被全局静态导入过的,各处写的动态导入零额外体积。**
场景 3:组件只被一处使用
js
// ─── LoginPage.vue ───
import HeavyChartLib from '@/components/HeavyChartLib.vue' // 500KB
打包结果:
LoginPage.\[hash].js ← 内含 HeavyChartLib(500KB),只有 LoginPage 用
如果改成动态:
js
// ─── LoginPage.vue ───
components: {
HeavyChartLib: () => import('@/components/HeavyChartLib.vue')
}
打包结果:
HeavyChartLib.\[hash].js ← 独立 chunk(500KB)
LoginPage.\[hash].js ← 不含 HeavyChartLib,体积更小
**结论:只在一处使用的组件,静态/动态对总包体积影响不大,但动态能让首屏更快。**
三、加载行为差异
静态导入
用户访问 PageA
↓
浏览器下载 PageA.\[hash].js(含所有静态导入的依赖)
↓
Vue 直接渲染,组件立即可用
↓
跳转 PageB
↓
浏览器下载 PageB.\[hash].js
动态导入
用户访问 PageA
↓
浏览器下载 PageA.\[hash].js(不含动态导入的组件)
↓
Vue 渲染到使用了动态组件的部分
↓
发起额外 HTTP 请求下载 BigTable.\[hash].js
↓
下载完成,组件显示
↓
跳转 PageB
↓
浏览器下载 PageB.\[hash].js
↓
PageB 也用到 BigTable ------ 从缓存取,无需再次下载
四、对比总表
| 维度 | 静态导入 | 动态导入 |
|---|---|---|
| 语法 | import X from './X' |
() => import('./X') |
| 执行时机 | 编译时(模块初始化前) | 运行时(渲染时才请求) |
| 写在哪儿 | 文件顶层 | 任意位置(函数/条件/循环) |
| 返回类型 | 模块引用(同步) | Promise(异步) |
| 同一组件 N 处引用 | 打包 N 份 | 打包 1 份 |
| 运行时加载 | 随父 chunk 一起下载 | 多一次 HTTP 请求 |
| 首屏可用性 | 立即可用 | 需要等待网络 |
| 路由懒加载 | 不支持 | 支持 |
| 已全局静态注册的组件 | --- | 不产生独立 chunk,零额外体积 |
五、选型建议
| 使用场景 | 推荐方式 | 理由 |
|---|---|---|
| 路由页面组件 | 动态导入 | 路由懒加载,首屏只加载当前页面 |
| 低频弹窗(点击才出现) | 动态导入 | 用户不点就不加载 |
全局注册的通用组件(global.js 已有) |
两者等效 | 已在主 chunk,无差别 |
| 高频使用、切换频繁的组件 | 静态导入 | 省去每次切换时的网络等待 |
| 同一个组件被 2+ 页面引用 | 动态导入 | 避免重复打包 |
| 只有 1 个页面用的小组件 | 两者等效 | 打包体积无差异 |
| 第三方库(element-ui、vxe-table) | 静态导入 | 按需引入或全量引入,不用动态分包 |
六、实际项目中的模式
js
// ✅ 推荐:路由懒加载用动态导入
{
path: '/salary-manage',
component: () => import('@/views/salary-manage/index.vue')
}
// ✅ 推荐:低频使用组件用动态导入
components: {
UploadAttach: () => import('@/components/UploadAttach/index.vue')
}
// ⚠️ 多余但无害:组件已在全局静态导入,再写动态
components: {
dialogAddEdit: () => import('@/components/form/addEdit') // ← 已在 global.js 静态导入
}
// ❌ 不推荐:高频切换的组件用动态导入
// 如果用户频繁在 A/B tab 间切换,每次切换都等网络
components: {
TabAComp: () => import('./TabAComp.vue'),
TabBComp: () => import('./TabBComp.vue')
}
// → 改为静态导入
import TabAComp from './TabAComp.vue'
import TabBComp from './TabBComp.vue'
七、怎么验证打包结果
bash
# 构建
npm run build
# 查看产物
# 打开 dist/assets/ 目录
# 静态导入的组件代码会混在页面 chunk 里
# 动态导入的组件会生成独立的 .js 文件
# 也可以看构建日志末尾的 chunk 列表:
# dist/assets/index.\[hash].js ← 主入口
# dist/assets/BigTable.\[hash].js ← 动态导入的独立 chunk
# dist/assets/PageA.\[hash].js ← 页面 chunk
八、一句话口诀
多处引用用动态,避免重复打包;
首屏路径用静态,省下一次请求;
全局已经注册过,动静态一样;
低频弹窗懒加载,分包体积最优。