import/export:前端模块化实战|JS 基础语法与数据操作篇

import/export:前端模块化实战|JS 基础语法与数据操作篇
【ES Modules】前端模块化实战:从代码拆分逻辑到落地实操,彻底搞懂import/export的最佳写法,避开模块化高频坑!
在这里插入图片描述

📑 文章目录

同学们好,我是 Eugene(尤金),一个拥有多年中后台开发经验的前端工程师~

(Eugene 发音很简单,/juːˈdʒiːn/,大家怎么顺口怎么叫就好)

你是否也有过:明明学过很多技术,一到关键时候却讲不出来、甚至写不出来?

你是否也曾怀疑自己,是不是太笨了,明明感觉会,却总差一口气?

就算想沉下心从头梳理,可工作那么忙,回家还要陪伴家人。

一天只有24小时,时间永远不够用,常常感到力不从心。

技术行业,本就是逆水行舟,不进则退。

如果你也有同样的困扰,别慌。

从现在开始,跟着我一起心态归零,利用碎片时间,来一次彻彻底底的基础扫盲

这一次,我们一起慢慢来,扎扎实实变强。

不搞花里胡哨的理论堆砌,只分享看得懂、用得上的前端干货,

咱们一起稳步积累,真正摆脱“面向搜索引擎写代码”的尴尬。


一、先体验一下:没有模块化有多难受

1.1 一个真实的场景

假设你要做一个「用户列表」页面,需要:

  • 格式化日期
  • 调用用户列表接口
  • 显示加载中状态

如果全写在一个文件里,大概是这样的:

<!-- index.html --><!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>用户列表</title></head><body><divid="app"></div><scriptsrc="./app.js"></script></body></html>
// app.js - 所有逻辑全塞在一起,有 100 多行constAPI_BASE='/api';const list =[];let loading =false;functionformatDate(date){returnnewDate(date).toLocaleDateString('zh-CN');}functiongetUserList(){ loading =true;fetch(API_BASE+'/user/list').then(res=> res.json()).then(data=>{ list = data;// 注意:这里 list 是 const,会报错!变量多了容易搞混 loading =false;});}// 假设还有 10 个其他函数...// 三个月后你回来改 bug,根本找不到 formatDate 在哪...

问题很快就出来了:

  1. 变量、函数全混在一起,改一处容易影响别处
  2. formatDate 如果别的页面也要用,只能复制粘贴
  3. 文件越来越长,找个函数要翻很久

模块化要解决的,就是这三个问题。

⬆ 返回目录

1.2 模块化能做到的三件事

  • 隔离:每个文件有自己的作用域,不互相污染
  • 依赖清晰:用 import 明确「谁用了谁」
  • 可维护:按职责拆文件,找代码、改代码都更轻松

后面我们就用 ES Modulesimport / export)来做这件事。

⬆ 返回目录


二、import / export 基础

2.1 先搞懂 export:导出

两种常见用法:

方式一:命名导出(一个文件可以导出多个)

// utils/format.jsexportfunctionformatDate(date){returnnewDate(date).toLocaleDateString('zh-CN');}exportfunctionformatMoney(num){return`¥${num.toFixed(2)}`;}

方式二:默认导出(一个文件只能有一个 default)

// config.jsexportdefault{apiBase:'/api',timeout:5000,};
  • 命名导出:可以导出很多个,import 时要用同名
  • 默认导出:只有一个,import 时可以随便起名字

⬆ 返回目录

2.2 再搞懂 import:引入

// 引入命名导出:名字必须和 export 的一致import{ formatDate, formatMoney }from'./utils/format.js';// 引入默认导出:名字可以随便起import config from'./config.js'; console.log(config.apiBase);// '/api'// 把整个模块当作对象引入import*as formatUtils from'./utils/format.js'; formatUtils.formatDate(newDate());

记住一个区别:

写法含义
import { foo }引入命名导出{} 是语法,不是解构
import foo引入默认导出

⬆ 返回目录


三、完整的可运行示例

下面是一个最小可运行项目,涵盖:utils、api、constants、composables,可以直接照抄跑起来。

3.1 项目结构

 my-project/ ├── index.html ├── package.json ├── vite.config.js └── src/ ├── main.js # 入口 ├── constants/ │ └── index.js # 常量 ├── utils/ │ ├── format.js # 格式化 │ └── index.js # 统一导出 ├── api/ │ ├── request.js # 请求封装 │ ├── user.js # 用户接口 │ └── index.js # 统一导出 ├── composables/ # Vue 组合式函数(可复用逻辑) │ └── useUserList.js └── App.vue # 根组件 

⬆ 返回目录

3.2 完整代码(可直接复制)

1. package.json

{"name":"module-demo","version":"1.0.0","type":"module","scripts":{"dev":"vite"},"dependencies":{"vue":"^3.4.0","axios":"^1.6.0"},"devDependencies":{"vite":"^5.0.0","@vitejs/plugin-vue":"^5.0.0"}}

2. index.html

<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>用户列表 - 模块化示例</title></head><body><divid="app"></div><scripttype="module"src="/src/main.js"></script></body></html>

3. src/constants/index.js

// 常量:放那些「写死不变」的值,以后改起来只改这一处exportconstAPI_BASE='/api';exportconstTIMEOUT=10000;// 订单状态:用常量代替魔法数字 0、1、2、3exportconstORDER_STATUS={PENDING:0,PAID:1,SHIPPED:2,COMPLETED:3,};exportconstORDER_STATUS_TEXT={0:'待支付',1:'已支付',2:'已发货',3:'已完成',};

4. src/utils/format.js

// 纯函数:同样的输入,一定得到同样的输出,不依赖外部状态exportfunctionformatDate(date, options ={}){if(!date)return'-';returnnewDate(date).toLocaleDateString('zh-CN',{year:'numeric',month:'2-digit',day:'2-digit',...options,});}exportfunctionformatMoney(num){if(num ==null||isNaN(num))return'¥0.00';return`¥${Number(num).toFixed(2)}`;}exportfunctionformatPhone(phone){if(!phone || phone.length !==11)return phone;return`${phone.slice(0,3)}****${phone.slice(7)}`;}

5. src/utils/index.js

// 统一导出:别人只需要 import from '@/utils' 就能拿到所有工具export{ formatDate, formatMoney, formatPhone }from'./format.js';

6. src/api/request.js

import axios from'axios';import{API_BASE,TIMEOUT}from'../constants/index.js';// 创建一个配置好的 axios 实例,所有请求都走它const request = axios.create({baseURL:API_BASE,timeout:TIMEOUT,});// 响应拦截器:统一处理错误和数据结构 request.interceptors.response.use((res)=> res.data,// 直接返回 data,调用方少写一层(err)=>{if(err.response?.status ===401){ console.warn('未登录,请先登录');// 实际项目:跳转登录页}return Promise.reject(err);});exportdefault request;

7. src/api/user.js

import request from'./request.js';// 每个函数对应一个接口,参数和返回值一目了然exportfunctiongetUserList(params ={}){return request.get('/user/list',{ params });}exportfunctiongetUserDetail(id){return request.get(`/user/${id}`);}

8. src/api/index.js

export*from'./user.js';// 以后有 order.js,再加一行:export * from './order.js';

9. src/composables/useUserList.js

import{ ref, watch }from'vue';import{ getUserList }from'../api/index.js';// 把「拉列表 + loading + error」抽成组合式函数,任何组件都能复用exportfunctionuseUserList(params ={}){const list =ref([]);const loading =ref(false);const error =ref(null);asyncfunctionfetchList(){ loading.value =true; error.value =null;try{const res =awaitgetUserList(params); list.value = res.data || res;}catch(err){ error.value = err.message;}finally{ loading.value =false;}}// 当 params 变化时重新请求watch(()=>[params.page, params.pageSize],()=>fetchList(),{immediate:true});return{ list, loading, error, fetchList };}

10. src/App.vue

<template><divv-if="loading">加载中...</div><divv-else-if="error">出错了:{{ error }}</div><divv-elsestyle="padding: 20px"><h1>用户列表</h1><ul><liv-for="user in list":key="user.id"> {{ user.name }} - 注册时间:{{ formatDate(user.createdAt) }} </li></ul></div></template><scriptsetup>import{ useUserList }from'./composables/useUserList.js';import{ formatDate }from'./utils/index.js';const{ list, loading, error }=useUserList({page:1,pageSize:10});</script>

11. src/main.js

import{ createApp }from'vue';import App from'./App.vue';createApp(App).mount('#app');

12. vite.config.js

import{ defineConfig }from'vite';import vue from'@vitejs/plugin-vue';exportdefaultdefineConfig({plugins:[vue()],});

⬆ 返回目录

3.3 怎么运行

cd my-project npminstallnpm run dev 

浏览器打开 http://localhost:5173 即可(接口需自行 mock 或接真实后端)。

⬆ 返回目录


四、这一套结构在解决什么问题

4.1 utils:通用纯函数

  • 职责:格式化、校验、防抖节流等和业务无关的函数
  • 原则:尽量纯函数,不依赖接口、路由、业务状态
  • 示例:formatDateformatMoneyisValidPhone

⬆ 返回目录

4.2 api:请求层

  • 职责:封装 axios/fetch,按业务拆接口文件
  • 原则:只负责发请求、返回数据,不写业务判断
  • 示例:getUserListgetUserDetail

⬆ 返回目录

4.3 composables:可复用逻辑

  • 职责:把「请求 + loading + error」等可复用逻辑抽成组合式函数
  • 原则:只在多处复用时抽,不要过度抽象
  • 示例:useUserListuseDebounce

⬆ 返回目录

4.4 constants:常量与配置

  • 职责:状态码、业务码、环境配置等
  • 原则:只放常量,不放逻辑
  • 示例:ORDER_STATUSAPI_BASE

⬆ 返回目录


五、常见坑

建议
utils 里写业务逻辑工具函数只做通用处理,不依赖具体业务
api 层写业务判断api 只管请求和错误,业务逻辑在组件/composable 里
魔法数字状态码、业务码用常量,如 ORDER_STATUS.PENDING
watch 依赖写错watch 的监听源要写对,否则会多请求或漏更新
ref 忘记 .value在 script 里访问 ref 要加 .value,模板中自动解包
循环依赖保持单向依赖:constants → api → composables → 组件

⬆ 返回目录


六、小结

模块化的目的就三点:职责清晰、依赖明确、好维护

先按 utils、api、composables、constants 这几类把代码分好,再按项目规模慢慢细化,不必一上来就设计得很复杂。

⬆ 返回目录


🔍 系列模块导航

📝 JS 基础语法与数据操作

一、《var/let/const:变量与作用域实战选型|JS 基础语法与数据操作篇》
二、《this、箭头函数与普通函数:前端实战避坑指南 | JS 基础语法与数据操作篇》
三、《对象解构赋值:接口数据解包 10 个实战写法|JS 基础语法与数据操作篇》
四、《map/filter/reduce:数组10个常用实战操作|JS 基础语法与数据操作篇》
五、《find/some/every/includes:数组查找与判断实战用法|JS 基础语法与数据操作篇》
六、《sort/localeCompare:对象数组排序与分组实战|JS 基础语法与数据操作篇》
七、《模板字符串 /split/join/ 正则:字符串处理实战|JS 基础语法与数据操作篇》
八、《Date/dayjs:日期时间处理实战|JS 基础语法与数据操作篇》
九、《try/catch/Promise:前端错误处理实战|JS 基础语法与数据操作篇》
十、《import/export:前端模块化实战|JS 基础语法与数据操作篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

📚 系列总览

前端体系化学习完全体:基础 → 规范 → 架构 → 大厂面试
四套系列、百余篇高质量实战文,从入门到进阶,一站式补齐前端核心能力
  • 前端基础实战系列《前端基础实战:JS/TS与Vue体系化扫盲(47 篇完整目录 + 避坑)》
  • 前端规范实战系列:工程化编码规范 + API / 交互 / 组件统一标准(持续更新中)
  • 前端架构实战系列:聚焦工程化、性能优化、可维护架构、中后台体系设计(规划中)
  • 前端大厂面试系列:覆盖高频考点、手写题、项目深挖、简历与面试技巧(规划中)

每个系列完结后,都会整理成一篇完整导航文并附上直达链接,方便大家按顺序、体系化学习。

全套内容持续更新中,敬请期待~
⬆ 返回目录


学习本就是一场持久战,不需要急着一口吃成胖子。哪怕今天你只记住了一点点,这都是实打实的进步。

后续我还会继续用这种大白话、讲实战方式,带大家扫盲更多前端基础。

关注我,不迷路,咱们把那些曾经模糊的知识点,一个个彻底搞清楚。

如果你觉得这篇内容对你有帮助,不妨点赞+收藏,下次写代码卡壳时,拿出来翻一翻,比搜引擎更靠谱。

我是 Eugene,你的电子学友,我们下一篇干货见~

Could not load content