Vue 3 模块导入教程:import、export 与文件组织

Vue 3 模块导入教程:import、export 与文件组织

在 Vue 3 项目中,一个页面通常会同时使用 Vue API、第三方 npm 包、.vue 组件、接口模块、工具函数、样式文件和配置文件。

这些内容不会自动出现在当前文件中,而是通过 ES Module 的 importexport 建立联系。

本文重点讲解:

  • importexport 的基本作用;
  • 默认导出与具名导出;
  • npm 包的导入方法;
  • .vue.js.ts 文件的导入方法;
  • @ 路径别名;
  • 静态导入、动态导入与路由懒加载;
  • 副作用导入;
  • main.ts 中的真实导入流程;
  • 常见导入错误与排查方法。

1、先理解 Vue 项目的模块化结构

现代 Vue 项目通常基于 ES Module,也就是 JavaScript 的模块系统。

每个文件都可以看作一个独立模块,例如:

text 复制代码
src/
├─ main.ts
├─ App.vue
├─ router/
│  └─ index.ts
├─ permission.ts
├─ layout/
│  ├─ index.vue
│  └─ components/
│     └─ AppMain.vue
├─ views/
│  └─ bof/
│     └─ heat/
│        ├─ index.vue
│        └─ twin.vue
└─ utils/
   └─ request.ts

这些文件不会自动认识彼此。

当一个文件需要使用另一个文件中的内容时,需要通过:

typescript 复制代码
import ...

进行导入。

当一个文件希望把自己的变量、函数、类或组件提供给其他文件使用时,需要通过:

typescript 复制代码
export ...

进行导出。

因此,模块化最基本的关系是:

text 复制代码
一个文件 export
        ↓
另一个文件 import
        ↓
导入后才能使用


2、import 到底是什么

import 的作用是从其他模块中引入内容。

最常见的格式是:

typescript 复制代码
import 导入内容 from '模块路径';

例如:

typescript 复制代码
import { createApp } from 'vue';

这表示:

从名为 vue 的包中,导入一个叫作 createApp 的成员。

再例如:

typescript 复制代码
import App from './App.vue';

这表示:

从当前目录的 App.vue 文件中,导入该文件的默认导出,并在当前文件中把它命名为 App

需要注意的是,下面两行虽然都使用 import,但导入目标不同:

typescript 复制代码
import { createApp } from 'vue';
import App from './App.vue';

第一行导入的是安装在项目中的 npm 包。

第二行导入的是项目内部文件。



3、导入 npm 包

3.1 什么是 npm 包

npm 包是通过 npm、pnpm 或 yarn 安装到项目中的第三方模块。

例如:

bash 复制代码
npm install vue
npm install vue-router
npm install three

安装后,依赖信息通常会记录在:

text 复制代码
package.json

实际包文件一般存放在:

text 复制代码
node_modules/

代码中导入 npm 包时,通常不写 ./../

typescript 复制代码
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import * as THREE from 'three';

判断规则可以暂时记成:

text 复制代码
路径以 ./ 或 ../ 开头
→ 项目内部相对路径

路径以 @/ 开头
→ 项目内部别名路径

路径直接写 vue、vue-router、three
→ npm 包

3.2 从 Vue 中导入函数

Vue 3 的组合式 API 通常使用具名导入:

typescript 复制代码
import {
  ref,
  computed,
  onMounted,
  onBeforeUnmount,
  nextTick
} from 'vue';

导入后才能在当前文件中调用:

typescript 复制代码
const count = ref(0);

const doubleCount = computed(() => {
  return count.value * 2;
});

onMounted(() => {
  console.log('组件已经挂载');
});

没有导入就直接使用,会出现类似错误:

text 复制代码
ref is not defined
onMounted is not defined

3.3 导入整个包

Three.js 经常使用下面的写法:

typescript 复制代码
import * as THREE from 'three';

这里的:

typescript 复制代码
* as THREE

表示把 three 包导出的内容集中放到 THREE 这个命名空间中。

使用时写成:

typescript 复制代码
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera();
const renderer = new THREE.WebGLRenderer();

这样做的好处是代码来源非常清晰:

text 复制代码
THREE.Scene
THREE.Mesh
THREE.Group
THREE.Vector3

一眼就能看出这些对象来自 Three.js。

Three.js 的扩展模块通常单独导入,例如:

typescript 复制代码
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

然后使用:

typescript 复制代码
const controls = new OrbitControls(
  camera,
  renderer.domElement
);


4、默认导出和具名导出

理解 import 时,最重要的知识之一就是区分:

text 复制代码
默认导出
具名导出

4.1 默认导出 export default

一个模块最多只能有一个默认导出。

例如:

typescript 复制代码
const router = createRouter({
  history: createWebHistory(),
  routes: []
});

export default router;

另一个文件导入时不需要花括号:

typescript 复制代码
import router from './router';

默认导入的本地名称可以修改:

typescript 复制代码
import appRouter from './router';

虽然原文件导出的变量叫 router,但当前文件可以把它命名为 appRouter

在团队项目中,通常仍建议保持含义一致:

typescript 复制代码
import router from './router';

这样更容易阅读。


4.2 具名导出 export

一个模块可以有多个具名导出。

例如创建一个工具文件:

typescript 复制代码
// src/utils/format.ts

export const formatTemperature = (
  value: number
): string => {
  return `${value.toFixed(1)} ℃`;
};

export const formatPressure = (
  value: number
): string => {
  return `${value.toFixed(2)} MPa`;
};

导入时必须使用花括号:

typescript 复制代码
import {
  formatTemperature,
  formatPressure
} from '@/utils/format';

使用时:

typescript 复制代码
const temperatureText =
  formatTemperature(1650.28);

const pressureText =
  formatPressure(0.85);

4.3 给具名导入改名

可以通过 as 修改本地名称:

typescript 复制代码
import {
  formatTemperature as formatTemp
} from '@/utils/format';

之后使用:

typescript 复制代码
formatTemp(1650);

这适合解决名称冲突,但不要无意义地频繁改名,否则代码会更难追踪。


4.4 同时使用默认导出和具名导出

一个文件可以同时提供默认导出和具名导出:

typescript 复制代码
export const ROUTE_NAME = 'BofHeatTwin';

const twinConfig = {
  fullScreen: true
};

export default twinConfig;

导入时可以写:

typescript 复制代码
import twinConfig, {
  ROUTE_NAME
} from '@/config/twin';


5、如何导入 .vue 文件

Vue 单文件组件使用 .vue 扩展名,通常包含:

vue 复制代码
<template>
  <!-- 页面结构 -->
</template>

<script setup lang="ts">
  // 页面逻辑
</script>

<style scoped>
  /* 页面样式 */
</style>

5.1 静态导入 Vue 组件

假设有一个组件:

text 复制代码
src/components/TwinHeader.vue

在页面中可以这样导入:

vue 复制代码
<script setup lang="ts">
import TwinHeader from '@/components/TwinHeader.vue';
</script>

<template>
  <TwinHeader />
</template>

<script setup> 中,导入后的组件可以直接在模板里使用,不需要再写:

typescript 复制代码
components: {
  TwinHeader
}

这是 Vue 3 <script setup> 的便利之处。


5.2 相对路径导入 .vue 文件

假设目录是:

text 复制代码
src/views/bof/heat/
├─ index.vue
├─ twin.vue
└─ components/
   └─ StatusPanel.vue

twin.vue 中导入 StatusPanel.vue

typescript 复制代码
import StatusPanel from './components/StatusPanel.vue';

这里:

text 复制代码
./

表示当前目录。

如果要从 components/StatusPanel.vue 返回上一级,再访问其他文件,可以使用:

text 复制代码
../

例如:

typescript 复制代码
import HeatCard from '../components/HeatCard.vue';

不过相对路径层级太深时会变得难以阅读:

typescript 复制代码
import HeatCard from '../../../../components/HeatCard.vue';

这时更适合使用路径别名。


5.3 使用 @ 路径别名

Vue 工程通常把:

text 复制代码
@

配置为:

text 复制代码
src

因此:

typescript 复制代码
import TwinHeader from '@/components/TwinHeader.vue';

等价于从:

text 复制代码
src/components/TwinHeader.vue

导入。

路由中的:

typescript 复制代码
component: () =>
  import('@/views/bof/heat/twin.vue')

实际指向:

text 复制代码
src/views/bof/heat/twin.vue

@ 不是 JavaScript 原生语法,而是构建工具提供的路径别名。

在 Vite 中,一般会在 vite.config.ts 中配置,例如:

typescript 复制代码
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'node:path';

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  }
});

TypeScript 项目还可能在 tsconfig.json 中配置:

json 复制代码
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  }
}

如果 Vite 配置了别名,但 TypeScript 没有对应配置,编辑器可能提示找不到模块。



6、如何导入 .js.ts 文件

Vue 页面中的大量业务逻辑不会全部写在 .vue 文件里,而会拆到普通 JavaScript 或 TypeScript 模块中。

例如:

text 复制代码
src/
├─ api/
│  └─ heat.ts
├─ utils/
│  └─ format.ts
├─ config/
│  └─ twin.ts
└─ permission.ts

6.1 导入工具函数

typescript 复制代码
import {
  formatTemperature
} from '@/utils/format';

如果构建工具支持扩展名自动解析,下面两种写法通常都可以:

typescript 复制代码
import {
  formatTemperature
} from '@/utils/format';

import {
  formatTemperature
} from '@/utils/format.ts';

在 Vue 和 Vite 项目中,一般更常见的是省略 .ts

typescript 复制代码
import {
  formatTemperature
} from '@/utils/format';

但导入 .vue 文件时通常保留 .vue

typescript 复制代码
import TwinHeader from './TwinHeader.vue';

6.2 导入接口请求模块

例如:

typescript 复制代码
// src/api/heat.ts

import request from '@/utils/request';

export const getHeatDetail = (
  heatId: string
) => {
  return request({
    url: `/bof/heat/${heatId}`,
    method: 'get'
  });
};

twin.vue 中导入:

typescript 复制代码
import {
  getHeatDetail
} from '@/api/heat';

调用:

typescript 复制代码
const loadTwinData = async () => {
  const response = await getHeatDetail(
    heatId.value
  );

  console.log(response);
};

这种拆分方式可以让职责更清晰:

text 复制代码
twin.vue
→ 管理页面状态和交互

api/heat.ts
→ 管理后端接口地址和请求方式

utils/request.ts
→ 管理 Axios 实例、请求头和异常处理

6.3 只执行文件,不接收导出内容

项目入口中可能出现:

typescript 复制代码
import './permission';

这叫作副作用导入。

它没有接收任何变量,作用是:

加载并执行 permission.ts 文件中的顶层代码。

例如 permission.ts 中注册了全局路由守卫:

typescript 复制代码
router.beforeEach((to, from, next) => {
  // 权限检查
});

当入口文件执行:

typescript 复制代码
import './permission';

这个守卫就会被注册。

常见的副作用导入还包括:

typescript 复制代码
import './styles/index.scss';
import './permission';
import './plugins/highlight';


7、静态导入和动态导入

7.1 静态导入

静态导入写在模块顶层:

typescript 复制代码
import App from './App.vue';
import router from './router';

它通常会在当前模块加载时被分析和处理。

适合:

  • 应用启动必需模块;
  • 通用组件;
  • 核心工具;
  • 全局样式;
  • Vue、Pinia、Router 等基础依赖。

7.2 动态导入

动态导入使用:

typescript 复制代码
import('模块路径')

它返回一个 Promise。

Vue Router 中常见的写法是:

typescript 复制代码
component: () =>
  import('@/views/bof/heat/twin.vue')

这叫作路由懒加载。

它表示:

text 复制代码
应用启动时
→ 暂时不加载 twin.vue

用户访问对应路由时
→ 再异步加载 twin.vue

好处包括:

  • 减少首页初始下载体积;
  • 缩短首次加载时间;
  • 大型页面按需加载;
  • Three.js、图表和数字孪生页面不会阻塞普通后台首页。

对于体积较大的三维页面,懒加载尤其重要。


7.3 静态导入和动态导入的对比

对比项 静态导入 动态导入
语法 import X from '...' import('...')
执行时机 模块加载阶段 运行到该逻辑时
返回内容 直接获得模块成员 返回 Promise
常见用途 核心依赖、普通组件 路由懒加载、按需功能
是否利于拆包 可以参与构建分析 更适合形成独立代码块


8、Vue 应用入口 main.ts

Vue 项目一般从 main.tsmain.js 启动。

典型代码如下:

typescript 复制代码
import { createApp } from 'vue';

import App from './App.vue';
import router from './router';
import store from './store';

import './permission';
import './styles/index.scss';

const app = createApp(App);

app.use(router);
app.use(store);

app.mount('#app');

这段代码可以拆成五步。


8.1 导入 Vue 的 createApp

typescript 复制代码
import {
  createApp
} from 'vue';

createApp() 用来创建 Vue 应用实例。


8.2 导入根组件

typescript 复制代码
import App from './App.vue';

App.vue 是整个组件树的根组件。


8.3 导入 Router 和 Store

typescript 复制代码
import router from './router';
import store from './store';

这里的:

typescript 复制代码
'./router'

通常会自动解析到:

text 复制代码
src/router/index.ts

这是模块目录解析的常见写法。


8.4 安装插件

typescript 复制代码
app.use(router);
app.use(store);

app.use() 用于把插件安装到 Vue 应用。

安装 Router 后,项目才能正常使用:

typescript 复制代码
router.push();
useRouter();
useRoute();
<router-view />

安装 Store 后,组件才能使用全局状态管理。


8.5 挂载应用

typescript 复制代码
app.mount('#app');

它把 Vue 应用挂载到 HTML 中的:

html 复制代码
<div id="app"></div>

最终关系是:

text 复制代码
index.html
└─ #app
   └─ Vue 应用
      └─ App.vue


9、常见 import 错误

24.1 默认导入和具名导入写反

原文件:

typescript 复制代码
export default router;

正确:

typescript 复制代码
import router from './router';

错误:

typescript 复制代码
import {
  router
} from './router';

原文件:

typescript 复制代码
export const getHeatDetail = () => {};

正确:

typescript 复制代码
import {
  getHeatDetail
} from '@/api/heat';

错误:

typescript 复制代码
import getHeatDetail from '@/api/heat';

24.2 路径层级写错

错误示例:

typescript 复制代码
import StatusPanel from
  '../components/StatusPanel.vue';

但真实文件可能在当前目录下:

typescript 复制代码
import StatusPanel from
  './components/StatusPanel.vue';

遇到路径问题时,要从当前文件所在目录开始计算,而不是从项目根目录凭感觉计算。


24.3 @ 别名没有配置

如果使用:

typescript 复制代码
import TwinHeader from
  '@/components/TwinHeader.vue';

但 Vite 或 TypeScript 没有配置别名,就会出现:

text 复制代码
Cannot find module
Failed to resolve import

24.4 文件名大小写不一致

Windows 文件系统有时对大小写不敏感,但 Linux 生产环境通常敏感。

例如真实文件是:

text 复制代码
TwinHeader.vue

错误写成:

typescript 复制代码
import TwinHeader from
  './twinHeader.vue';

本地可能暂时没有暴露问题,部署到 Linux 后可能构建失败。


24.5 循环依赖

例如:

text 复制代码
router/index.ts
→ import permission.ts

permission.ts
→ import router/index.ts

如果设计不当,可能形成循环依赖。

当前更常见的结构是:

text 复制代码
main.ts
├─ import router
└─ import permission

permission.ts
└─ import router

要避免让 router/index.ts 再反向导入 permission.ts


24.6 动态导入路径无法被构建工具分析

推荐:

typescript 复制代码
const pages = {
  twin: () =>
    import('@/views/bof/heat/twin.vue'),
  heat: () =>
    import('@/views/bof/heat/index.vue')
};

不推荐随意拼接完全动态路径:

typescript 复制代码
const pageName = 'twin';

import(
  `@/views/bof/heat/${pageName}.vue`
);

构建工具可能无法可靠确定需要打包哪些文件。



10、模块导入学习检查清单

完成本文后,应能够回答下面的问题:

  • import App from './App.vue' 导入的是什么?
  • import { ref } from 'vue' 为什么有花括号?
  • import './permission' 为什么没有变量名?
  • 默认导出和具名导出有什么区别?
  • 如何导入 .vue.js.ts 文件?
  • @/views/bof/heat/twin.vue 中的 @ 是什么?
  • 相对路径中的 ./../ 分别表示什么?
  • 静态导入和动态导入有什么区别?
  • 为什么路由页面常使用 () => import(...)
  • 为什么文件名大小写在 Linux 生产环境中特别重要?
  • 什么是副作用导入?
  • 什么情况下可能出现循环依赖?

11、总结

Vue 3 项目由许多彼此独立的模块组成,export 负责把模块中的能力提供出去,import 负责在其他文件中引入这些能力。

最核心的规则可以概括为:

text 复制代码
默认导出
→ import 名称 from '路径'

具名导出
→ import { 名称 } from '路径'

副作用导入
→ import '路径'

npm 包
→ import ... from 'vue'

项目内部文件
→ import ... from './文件'
→ import ... from '@/文件'

路由懒加载
→ () => import('@/views/页面.vue')

在实际 Vue 项目中,应先看懂 main.ts 中导入了哪些基础模块,再沿着组件、工具文件、接口文件和路由文件逐步阅读。只要能够判断"内容从哪个模块导出、在什么位置导入、使用的是默认导出还是具名导出",就已经掌握了大型 Vue 项目代码关系的基础。

相关推荐
编程风暴1 小时前
3类人群的数据分析实习类型选择+4条选错红线
java·前端·数据分析
Bigger1 小时前
堆了 20 套主题、10+ 组件,用户还是用不出来?我给设计 Skill 加了「自动驾驶」
前端·ai编程·视觉设计
weipt1 小时前
springboot项目运行的几种方式
javascript·css·html
前端技术官2 小时前
前端真的比后端简单吗?
javascript·webview·dom·前端工程化·浏览器兼容性
vx-程序开发2 小时前
【java项目分享】springboot农产品销售平台14952
java·javascript·spring boot·python·eclipse·django·php
贾天佑忆月 迷失的昵2 小时前
.NET Web开发技术简单整理
前端·.net
布兰妮甜2 小时前
Vue 路由进阶:路由守卫权限控制、动态路由、懒加载、路由缓存
vue.js·懒加载·动态路由·路由守卫权限控制·路由缓存
Cobyte2 小时前
Vite & Rollup 插件开发实践
前端·javascript·vue.js
朝阳392 小时前
react19【系列实用教程】实用组件封装
开发语言·前端·javascript