零基础鸿蒙应用开发第二十节:工程化开发核心-文件导入与导出
【学习目标】
- 理解ArkTS模块的核心概念,知道“文件即模块”的基本规则
- 掌握
export(导出)的3种常用方式,能按需暴露文件内的函数/接口/变量 - 掌握
import(导入)的3种常用方式,能正确引用其他文件的内容 - 基于全新的
ModuleDemo工程,理解模块拆分的意义和最佳实践
【学习重点】
- 导出核心:按需导出(常用)、默认导出的语法和区别
- 导入核心:精准导入、整体导入、导入重命名的使用场景
- 工程实践:按功能拆分模块(如utils目录),提升代码复用性和可维护性
- 避坑要点:路径书写、重复导出/导入、默认导出的引用规则
- 命名规范:文件夹小驼峰、代码文件大驼峰的落地执行
一、工程结构
本节我们将创建名为ModuleDemo的工程,基于 鸿蒙5.0(API12) 开发,使用 DevEco Studio 6.0+ 工具,项目结构目录如下:
ModuleDemo/ # 工程根目录
├── entry/ # 主应用模块(核心代码目录)
│ ├── src/
│ │ ├── main/
│ │ │ ├── ets/ # ArkTS代码核心目录
│ │ │ │ ├── entryability/ # 应用入口(自动生成,无需修改)
│ │ │ │ ├── pages/ # 页面组件目录(自动生成,小驼峰)
│ │ │ │ │ └── Index.ets # 主页面(大驼峰,鸿蒙自动生成默认)
│ │ │ │ └── utils/ # 工具模块目录(小驼峰)
│ │ │ │ ├── MathUtils.ets # 数学工具模块(大驼峰)
│ │ │ │ └── UserUtils.ets # 用户工具模块(大驼峰)
│ │ │ ├── resources/ # 资源目录(自动生成,小驼峰)
│ │ │ └── module.json5 # 模块配置文件(自动生成)
│ │ ├── build-profile.json5 # 工程构建配置(自动生成)
│ │ └── hvigorfile.ts # 构建脚本(自动生成)
└── oh_modules/ # 第三方依赖目录(自动生成,小驼峰)
目录说明
pages/Index.ets:仅负责页面UI渲染和用户交互,通过import调用工具模块的功能;utils/:存放通用、可复用的功能模块(小驼峰文件夹),通过export暴露内容,是模块化的核心载体;- 所有
.ets文件都是独立模块,目录层级决定导入时的路径写法; - 命名规范(核心):
- 文件夹:统一使用小驼峰(如
utils/pages/entryability),多个单词拼接时第二个单词首字母小写; - 代码文件:统一使用大驼峰(帕斯卡命名)(如
MathUtils.ets/UserUtils.ets),多个单词拼接时每个单词首字母大写; - 特殊说明:
Index.ets是鸿蒙工程自动生成的默认页面,符合大驼峰规范,无需修改。
- 文件夹:统一使用小驼峰(如
二、模块的核心概念
ArkTS的模块系统本质是“文件级别的代码隔离与复用”——一个.ts/.ets文件就是一个独立模块,文件内的变量、函数、接口默认是“私有”的(只能在当前文件使用),只有通过export显式导出后,其他文件才能通过import引用。
举个直观例子:
utils/MathUtils.ets里的add函数,默认只能在这个文件内执行;- 给函数加
export导出后,pages/Index.ets才能通过import引入,点击页面按钮时调用该函数。
模块系统的核心价值:
- 拆分代码:把数学计算、用户信息处理拆到不同文件,避免单个文件代码臃肿;
- 复用代码:一处定义(如加法函数),所有页面都能通过导入复用,无需重复编写;
- 隔离作用域:不同模块的变量/函数名可重复(如MathUtils和UserUtils都有
format函数),不会互相冲突。
三、导出export:让其他文件能用到当前模块的内容
export是“向外暴露模块内容”的语法,ArkTS有3种常用导出方式,优先推荐按需导出。
3.1 方式1:按需导出(推荐)
在要导出的函数/接口/变量前直接加export,可同时导出多个内容,是最常用的方式。
示例(utils/MathUtils.ets)
// utils/MathUtils.ets(大驼峰文件名)
// 1. 导出函数(核心业务逻辑)
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
// 2. 导出接口(类型约束)
export interface CalculationResult {
operation: string; // 操作类型(加/乘)
result: number; // 计算结果
}
// 3. 导出常量(少用,优先用函数/接口)
export const PI = 3.1415926;
3.2 方式2:批量导出(按需导出的另一种写法)
先在文件内定义所有内容(不加export),最后集中通过export { 内容1, 内容2 }导出,适合需要集中管理导出内容的场景。
示例(utils/UserUtils.ets)
// utils/UserUtils.ets(大驼峰文件名)
// 先定义内容(无export)
function formatAge(age: number): string {
return age >= 18 ? "成年" : "未成年";
}
interface User {
id: number;
name: string;
age: number;
}
// 批量导出(等价于方式1)
export { formatAge, User };
3.3 方式3:默认导出(仅一个)
用export default导出模块的“核心内容”,一个模块只能有一个默认导出,适合“模块仅暴露一个核心功能”的场景(新手慎用,易混淆)。
示例(utils/MathUtils.ets)
// utils/MathUtils.ets
// 先保留原有按需导出内容,新增默认导出(一个文件仅一个)
export default function calculateAverage(numbers: number[]): number {
if (!Array.isArray(numbers) || numbers.some(item => typeof item !== 'number')) {
console.error('参数必须是数字数组');
return 0;
}
if (numbers.length === 0) return 0;
const sum = numbers.reduce((total, num) => total + num, 0);
return sum / numbers.length;
}
导出注意事项
- 按需导出可多个,默认导出仅一个,不可重复定义
export default; - 模块内私有内容(仅自己用)无需加
export,避免不必要的暴露; - 接口/类型建议和相关函数放在同一模块导出,方便引用;
- 导出内容命名:函数/变量用小驼峰(如
add/calculateAverage),接口/类用大驼峰(如CalculationResult/User),与文件/文件夹命名规范呼应。
四、导入(import):使用其他模块导出的内容
import是“引入外部模块内容”的语法,写法和导出方式一一对应,核心是写对文件路径(新手最易踩坑),且路径需严格匹配文件/文件夹的命名(包括大小写)。
4.1 方式1:精准导入(对应按需导出,推荐)
只导入需要的内容,减少代码体积,是工程化开发的首选。
示例(pages/Index.ets)
// pages/Index.ets
// 路径规则:../ 表示上一级目录(pages → utils),无需写.ets后缀,文件名匹配大驼峰
import { add, multiply, CalculationResult } from '../utils/MathUtils';
import { formatAge, User } from '../utils/UserUtils';
@Entry
@Component
struct Index {
aboutToAppear(): void {
const result = multiply(2,2)
console.log(`${result}`)
}
build() {
Column({ space: 15 }) {
// 调用数学工具函数
Button("计算10+20")
.width(280)
.height(60)
.onClick(() => {
const result: CalculationResult = {
operation: "加法",
result: add(10, 20)
};
console.log(`${result.operation}结果:${result.result}`); // 输出30
});
// 调用用户工具函数
Button("判断用户年龄状态")
.width(280)
.height(60)
.onClick(() => {
const user: User = { id: 101, name: "张三", age: 22 };
console.log(`${user.name}的年龄状态:${formatAge(user.age)}`); // 输出成年
});
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
4.2 方式2:导入重命名(解决命名冲突)
如果不同模块导出的内容重名(如MathUtils和UserUtils都有format函数),用as给导入的内容重命名,避免冲突。
示例(pages/Index.ets)
// pages/Index.ets
// 演示:给MathUtils写入formatAge函数,重命名避免冲突
import { formatAge as formatUserAge } from '../utils/UserUtils';
import { formatAge as formatMathAge } from '../utils/MathUtils';
@Entry
@Component
struct Index {
build() {
Column({ space: 15 }) {
Button("格式化用户年龄")
.onClick(() => console.log(formatUserAge(17))); // 输出未成年
Button("格式化数学年龄标识")
.onClick(() => console.log(formatMathAge(17)));
}
.justifyContent(FlexAlign.Center);
}
}
4.3 方式3:整体导入(对应批量导出/默认导出)
用import * as 模块别名导入模块所有导出内容,适合需要用到模块多个内容的场景;默认导出的内容需单独导入(无需{})。
示例1:整体导入所有内容(pages/Index.ets)
// pages/Index.ets
// 导入MathUtils的所有导出内容,用MathTools前缀调用
import * as MathTools from '../utils/MathUtils';
@Entry
@Component
struct Index {
build() {
Column({ space: 15 }) {
Button("计算5×8")
.onClick(() => console.log(`${MathTools.multiply(5, 8)}`)); // 输出40
Button("打印圆周率")
.onClick(() => console.log(`${MathTools.PI}`)); // 输出3.1415926
}
.justifyContent(FlexAlign.Center);
}
}
示例2:导入默认导出的内容(pages/Index.ets)
// pages/Index.ets
// 默认导出的内容无需{},名称可自定义
import calculateAverage from '../utils/MathUtils';
@Entry
@Component
struct Index {
build() {
Column() {
Button("计算[1,2,3,4]的平均值")
.onClick(() => {
const avg = calculateAverage([1,2,3,4]);
console.log("平均值:", avg); // 输出2.5
});
}
.justifyContent(FlexAlign.Center);
}
}
4.3.1 混合导入(默认导出+按需导出)
实际开发中常需要同时导入模块的“默认导出内容”和“按需导出内容”,语法上无需嵌套,直接用逗号分隔即可,是高频实用写法。
示例(pages/Index.ets)
// pages/Index.ets
// 同时导入默认导出的calculateAverage + 按需导出的add
import calculateAverage, { add } from '../utils/MathUtils';
@Entry
@Component
struct Index {
build() {
Column() {
Button("先加后算平均值")
.onClick(() => {
// 先调用按需导出的add函数
const sum = add(10, 20);
// 再调用默认导出的calculateAverage函数
const avg = calculateAverage([sum, 30, 40]);
console.log("混合计算结果:", avg); // 输出 (30+30+40)/3 ≈ 33.33
});
}
.justifyContent(FlexAlign.Center);
}
}
导入路径书写规则
| 导入场景 | 路径写法示例 | 核心说明 |
|---|---|---|
| 导入同级目录文件 | import { xxx } from './Test' |
./ 代表“当前目录”,文件名匹配大驼峰 |
| 导入上级目录文件 | import { xxx } from '../utils/MathUtils' |
../ 代表“上一级目录”,文件夹匹配小驼峰、文件匹配大驼峰 |
| 导入多级上级目录文件 | import { xxx } from '../../utils/MathUtils' |
../../ 代表“上两级目录”,命名规则同上 |
五、工程化实践:按功能拆分模块
ModuleDemo工程是“按功能拆分模块”的基础示例,核心思路也是HarmonyOS开发的最佳实践,且严格遵循“文件夹小驼峰、代码文件大驼峰”的命名规范:
5.1 拆分原则
- utils目录(小驼峰):按功能拆分大驼峰文件(MathUtils/ UserUtils),每个文件只负责一类功能(数学计算/用户信息处理),导出相关函数/接口;
- pages目录(小驼峰):只做“UI渲染+事件绑定”,通过
import引用utils的功能,不写复杂业务逻辑; - 入口目录(entryability,小驼峰):仅负责应用启动,不耦合任何业务逻辑;
- 命名一致性:全工程严格遵循“文件夹小驼峰、文件大驼峰”,避免因命名混乱导致路径错误。
5.2 拆分优势
- 可维护性:修改加法函数只需改
MathUtils.ets,不影响用户信息处理和页面代码; - 可复用性:其他页面想计算平均值,直接导入
calculateAverage即可,无需重复写代码; - 可读性:一眼能通过文件名/文件夹名定位功能(如
utils/MathUtils.ets对应数学计算),新手易上手; - 兼容性:符合鸿蒙工程的命名习惯,降低团队协作时的沟通成本。
六、避坑指南
6.1 常见错误及解决方案
| 错误类型 | 报错特征 | 原因 | 解决方案 |
|---|---|---|---|
| 路径错误 | “找不到模块”“模块不存在” | 少写../、文件名/文件夹名大小写错误、加了.ets后缀 | 核对路径,用DevEco自动补全路径,严格匹配小驼峰文件夹/大驼峰文件的大小写 |
| 命名错误 | “不存在的导出成员” | 导入名称和导出名称不一致(如add写成plus) | 严格匹配名称,或用as重命名 |
| 重复导出 | 编译报错“重复导出” | 同一内容既按需导出又默认导出 | 一个内容仅导出一次,优先用按需导出 |
| 多默认导出 | 编译报错“多个默认导出” | 一个模块写了多个export default |
保留一个默认导出,其余用按需导出 |
6.2 新手开发建议
- 优先使用“按需导出+精准导入”,这是ArkTS工程化的标准写法;
- 严格遵循命名规范:文件夹小驼峰(如
utils/pages)、代码文件大驼峰(如MathUtils.ets/UserUtils.ets),全程统一风格; - 功能相近的内容放在同一模块(如所有数学计算函数都在MathUtils.ets),避免模块过多;
- 写导入路径时,利用DevEco Studio的“自动补全”功能(输入
../后按Tab),减少手动输入的大小写/层级错误。
七、内容总结
- 核心规则:ArkTS中“一个文件=一个模块”,内容默认私有,需
export导出才能被import引用; - 导出优先级:按需导出(多个)> 批量导出 > 默认导出(仅一个核心内容);
- 导入关键:精准导入优先,路径用
../(上级)/./(同级),无需写.ets后缀,严格匹配小驼峰文件夹/大驼峰文件的大小写; - 命名规范:文件夹小驼峰、代码文件大驼峰,是鸿蒙工程易维护、低冲突的核心保障;
- 避坑重点:规避路径/命名大小写错误(用自动补全)。
八、代码仓库
- 工程名称:ModuleDemo
- 仓库地址:https://gitee.com/juhetianxia321/harmony-os-code-base.git
九、下节预告
本节我们熟悉了文件导入导出,简单了解了模块的划分,前期我们已经掌握了一大半ArkTS基础知识。下一节我们开启新的小阶段从面向过程正式进入面向对象开发重点学习:
- 类(模板)与对象(实例)的核心关系,理解面向对象编程思维在鸿蒙开发中的优势;
- ArkTS类的标准定义语法,包括属性声明、构造函数与实例方法的规范写法;
this关键字的含义与使用场景,规避属性访问的常见错误;- 封装的核心思想,通过
public/private/protected访问修饰符实现数据安全控制; - getter/setter方法在属性安全读写中的应用,为后续鸿蒙业务数据模型设计夯实基础。
浙公网安备 33010602011771号