Day 15 - ArkTS 模块系统
目标:掌握模块导出导入、命名空间,理解 ArkTS 模块化编程模型
预计时间:1.5-2小时
课前思考
回顾前面学习的类和接口:
// 文件:Index.ets
class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
class Logger {
log(message: string): void {
console.log(message);
}
}
class Database {
query(sql: string): object[] {
return [];
}
}
思考问题:
- 当代码量增长到几千行时,单文件维护有什么困难?
- 多个类定义在同一个文件中,如何避免命名冲突?
- 如何复用其他文件中的工具函数或类?
第一部分:为什么需要模块
1.1 单文件的局限
问题:代码增长后的维护困难
// 所有代码挤在一个文件里...
// 工具函数(200行)
// 数据模型类(300行)
// 业务逻辑类(500行)
// UI组件类(400行)
// 总计:1400+ 行,难以定位和维护
维护痛点:
- 查找代码困难
- 多人协作冲突
- 编译时间变长
- 无法按需加载
1.2 模块化的好处
| 好处 | 说明 |
|---|---|
| 代码复用 | 写好的工具类可以在多个项目中使用 |
| 职责分离 | 每个文件只负责一类功能 |
| 命名隔离 | 不同文件的同名变量不会冲突 |
| 依赖清晰 | 通过导入语句明确知道依赖哪些模块 |
1.3 ArkTS 的模块模型
核心规则:一文件一模块
// 文件:math.ets
// 这是一个模块
// 文件:logger.ets
// 这是另一个模块
对标 C++ 的多文件编译:
| C++ | ArkTS |
|---|---|
.h 头文件声明 |
export 导出声明 |
.cpp 源文件实现 |
.ets 文件(声明+实现) |
#include "xxx.h" |
import { xxx } from './xxx' |
#pragma once |
模块单例(自动处理) |
namespace |
namespace(概念相同) |
第二部分:命名导出与导入
2.1 export 导出
问题:如何让其他文件使用本文件的类或函数?
导出变量:
// 文件:config.ets
// 导出常量
export const PI: number = 3.14159;
export const APP_NAME: string = "MyApp";
export const MAX_SIZE: number = 1024;
// 导出变量
export let debugMode: boolean = false;
export function setDebug(mode: boolean): void {
debugMode = mode;
}
导出函数:
// 文件:utils/math.ets
// 命名导出函数
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
export function divide(a: number, b: number): number {
if (b === 0) {
throw new Error("除数不能为零");
}
return a / b;
}
导出类:
// 文件:models/User.ets
export class User {
private id: number;
private name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
getId(): number {
return this.id;
}
getName(): string {
return this.name;
}
toString(): string {
return `User(${this.id}, ${this.name})`;
}
}
导出接口和枚举:
// 文件:types/index.ets
// 导出接口
export interface Shape {
area(): number;
perimeter(): number;
}
export interface Drawable {
draw(): void;
}
// 导出枚举
export enum Color {
RED = "#FF0000",
GREEN = "#00FF00",
BLUE = "#0000FF"
}
export enum Status {
PENDING,
PROCESSING,
COMPLETED
}
2.2 import 导入
问题:如何使用其他文件导出的内容?
// 文件:main.ets
import { add, subtract } from './utils/math';
import { User } from './models/User';
import { PI, APP_NAME } from './config';
// 使用导入的内容
let sum: number = add(10, 20);
console.log(`结果:${sum}`);
let user: User = new User(1, "张三");
console.log(`用户:${user.toString()}`);
console.log(`应用名:${APP_NAME}`);
导入多个成员:
// 文件:geometry.ets
import { Shape, Color, Status } from './types/index';
// 使用 Shape 接口
class Circle implements Shape {
private radius: number;
constructor(radius: number) {
this.radius = radius;
}
area(): number {
return PI * this.radius * this.radius;
}
perimeter(): number {
return 2 * PI * this.radius;
}
}
// 使用枚举
let color: Color = Color.RED;
let status: Status = Status.PENDING;
2.3 重命名导入导出
问题:导入的名称和本地变量冲突怎么办?
使用 as 关键字重命名:
// 文件:math.ets
export function log(message: string): void {
console.log(`[Math] ${message}`);
}
// 文件:logger.ets
export function log(message: string): void {
console.log(`[Logger] ${message}`);
}
// 文件:main.ets
// 重命名导入,避免冲突
import { log as mathLog } from './math';
import { log as loggerLog } from './logger';
// 同时使用两个不同来源的 log 函数
mathLog("计算结果");
loggerLog("系统消息");
导出时重命名:
// 文件:utils.ets
function internalHelper(): void {
console.log("内部辅助函数");
}
// 导出时重命名
export { internalHelper as helper };
对标 C++ namespace 别名:
// C++
namespace MyLongNamespaceName {
void func() {}
}
// 创建别名
namespace Short = MyLongNamespaceName;
// 使用
Short::func();
// ArkTS
import { MyLongFunctionName as short } from './module';
short(); // 使用短名称
2.4 批量导入
问题:一个模块导出很多内容,如何一次性导入?
// 文件:utils/index.ets
export function funcA(): void { }
export function funcB(): void { }
export function funcC(): void { }
export const CONST_A: number = 1;
export const CONST_B: number = 2;
// 文件:main.ets
// 批量导入所有导出内容
import * as Utils from './utils/index';
// 通过命名空间访问
Utils.funcA();
Utils.funcB();
console.log(`常量:${Utils.CONST_A}`);
对标 C++:
// C++
using namespace std; // 导入整个命名空间
// ArkTS
import * as Utils from './utils'; // 类似 using namespace
2.5 as 关键字
as 关键字有两个主要用途:
用途1:重命名导入/导出
// 导入时重命名,避免命名冲突
import { log as mathLog } from './math';
import { log as loggerLog } from './logger';
mathLog("计算结果");
loggerLog("系统消息");
// 导出时重命名
function internalHelper(): void { }
export { internalHelper as helper };
用途2:类型断言(Type Assertion)
问题:当你比编译器更清楚某个值的类型时
// 编译器不知道具体类型
let value: unknown = getSomeValue();
// 你确定这是 string 类型
let str: string = value as string; // 告诉编译器:相信我,这是 string
对标 C++ 强制类型转换:
| C++ | ArkTS |
|---|---|
int x = (int)y; |
let x = y as number; |
int x = static_cast<int>(y); |
let x = y as number; |
区别:
- C++ 强制转换会改变运行时行为
- ArkTS
as只是告诉编译器"我知道这是什么类型",编译后消失
警告: 滥用 as 会绕过类型检查,可能导致运行时错误。
2.6 关于 module 关键字
重要:module 关键字在 ArkTS/TypeScript 中已被废弃,现代代码中不再使用。
历史背景
早期 TypeScript 使用 module 关键字:
// 旧语法,已废弃
module Geometry {
export class Point { }
export class Line { }
}
现代 TypeScript/ArkTS 使用 namespace 代替:
// 新语法,推荐使用
namespace Geometry {
export class Point { }
export class Line { }
}
为什么废弃?
| 原因 | 说明 |
|---|---|
| 概念混淆 | module 既指文件模块,又指命名空间,容易混淆 |
| 标准化 | ES6 引入了标准的 import/export 模块系统 |
| 清晰区分 | namespace 专门用于代码组织,module 专门指文件模块 |
现在的规范
| 概念 | 关键字 | 用途 |
|---|---|---|
| 文件模块 | export / import |
跨文件代码复用 |
| 命名空间 | namespace |
单文件内代码组织 |
你可能会在哪里看到 module
1. 类型声明文件(.d.ts)
// 声明模块,告诉编译器这个文件是什么模块
declare module "my-library" {
export function doSomething(): void;
}
2. 配置文件中
// tsconfig.json
{
"compilerOptions": {
"module": "ES2020" // 指定模块系统
}
}
总结:在 ArkTS 代码中,你不需要使用 module 关键字。
2.7 处理大量导出的最佳实践
问题:如果一个模块导出了大量符号(如1万个),如何高效导入?
方案1:批量导入(命名空间导入)
// 导入所有导出内容到一个命名空间
import * as Utils from './utils/index';
// 通过命名空间访问
Utils.funcA();
Utils.funcB();
console.log(Utils.CONST_A);
优点: 一行代码导入所有内容
缺点: 使用时需要加前缀 Utils.
方案2:统一出口(re-export)
// utils/index.ets - 只导出常用的
export { add, sub, mul, div } from './math';
export { trim, upper } from './string';
// 其他不常用的不导出,需要时单独导入
使用者按需导入:
// 导入常用功能
import { add, trim } from './utils/index';
// 不常用的单独导入
import { complexFunction } from './utils/advanced';
方案3:模块化拆分
不要把大量符号放在一个模块里,按功能拆分:
utils/
├── math.ets // 数学相关
├── string.ets // 字符串相关
├── array.ets // 数组相关
├── network.ets // 网络相关
└── index.ets // 统一出口
实际项目中的最佳实践
| 场景 | 推荐方案 |
|---|---|
| 工具库(如 lodash) | 命名空间导入 import * as _ from 'lodash' |
| 自己的业务模块 | 统一出口 + 按需导入 |
| 大型框架 | 模块化拆分,按需导入子模块 |
第三部分:默认导出与导入
3.1 export default
问题:一个模块主要导出的是一个类,如何简化导入?
默认导出:
// 文件:Calculator.ets
export default class Calculator {
private value: number = 0;
add(n: number): Calculator {
this.value = this.value + n;
return this;
}
subtract(n: number): Calculator {
this.value = this.value - n;
return this;
}
getValue(): number {
return this.value;
}
reset(): void {
this.value = 0;
}
}
规则:
- 每个模块最多只能有一个默认导出
- 默认导出不需要名称(但通常会给一个)
3.2 导入默认导出
// 文件:main.ets
// 导入默认导出,可以任意命名!
import X from './Calculator'; // 命名为 X
import MyCalc from './Calculator'; // 命名为 MyCalc
import Calc from './Calculator'; // 命名为 Calc
// 甚至可以重复导入同一个模块,给同一个类取不同的名字
let calc1: X = new X();
let calc2: MyCalc = new MyCalc();
let calc3: Calc = new Calc();
// 这三个实例类型完全相同,都是 Calculator 类型
calc1.add(10);
calc2.subtract(5);
console.log(`结果:${calc3.getValue()}`); // 5
对比命名导入:
// 命名导出 - 必须有花括号,名称必须匹配
import { Calculator } from './Calculator';
// 默认导出 - 没有花括号,可以任意命名
import Calculator from './Calculator';
import MyCalculator from './Calculator'; // 同一个模块,不同名字
关键特性:
- 默认导出的导入名称完全自由,不需要和原类名一致
- 可以重复导入同一个默认导出,给同一个类取不同的名字
- 这些不同的名字指向的是同一个类型,可以互换使用
3.3 默认导出 vs 命名导出
| 特性 | 命名导出 | 默认导出 |
|---|---|---|
| 数量 | 一个模块可有多个 | 一个模块只能有一个 |
| 导入语法 | import { A } from '...' |
import A from '...' |
| 导入名称 | 必须匹配导出名称 | 可以任意命名 |
| 适用场景 | 工具函数库、多个相关类 | 单一主要功能、组件类 |
最佳实践:
// 推荐:工具类库使用命名导出
// 文件:math.ets
export function add(a: number, b: number): number { return a + b; }
export function sub(a: number, b: number): number { return a - b; }
export function mul(a: number, b: number): number { return a * b; }
// 推荐:单一组件使用默认导出
// 文件:DatabaseManager.ets
export default class DatabaseManager {
// 数据库管理的主要类
}
第四部分:模块路径与加载规则
4.1 相对路径
问题:如何指定要导入的模块位置?
核心原则:以当前文件所在位置为参考,找到目标模块
假设项目结构如下:
src/
├── main.ets ← 位置:src/
├── utils/
│ ├── math.ets ← 位置:src/utils/
│ └── helpers/
│ └── string.ets ← 位置:src/utils/helpers/
├── models/
│ └── User.ets ← 位置:src/models/
└── services/
└── api.ets ← 位置:src/services/
从 main.ets 导入(位于 src/):
// 文件:main.ets
// 当前目录下的直接子目录
import { add } from './utils/math';
// 当前目录下的多级子目录
import { trim } from './utils/helpers/string';
// 当前目录下的其他子目录
import { User } from './models/User';
import { apiService } from './services/api';
从 utils/helpers/string.ets 导入(位于 src/utils/helpers/):
// 文件:utils/helpers/string.ets
// 上级目录(回到 utils/)
import { add } from '../math';
// 上两级目录(回到 src/),再进入其他目录
import { User } from '../../models/User';
// 上两级目录,再进入 services/
import { apiService } from '../../services/api';
路径规则总结:
| 路径 | 含义 |
|---|---|
./xxx |
当前目录下的模块 |
./a/b/xxx |
当前目录下多级子目录的模块 |
../xxx |
上级目录下的模块 |
../../xxx |
上两级目录下的模块 |
记忆口诀:
./= 往下走(进入子目录)../= 往上走(回到父目录)- 每多一级
../= 多往上走一层
4.2 模块单例特性
问题:一个模块被多次导入,会执行几次?
// 文件:singleton.ets
console.log("模块初始化执行");
export let instanceCount: number = 0;
export function increment(): void {
instanceCount = instanceCount + 1;
}
// 文件:a.ets
import { instanceCount, increment } from './singleton';
increment();
console.log(`A: ${instanceCount}`); // 1
// 文件:b.ets
import { instanceCount, increment } from './singleton';
increment();
console.log(`B: ${instanceCount}`); // 2
// 文件:main.ets
import './a';
import './b';
import { instanceCount } from './singleton';
console.log(`Main: ${instanceCount}`); // 2
// 注意:singleton.ets 只执行了一次!
重要特性:
- 同一模块在程序生命周期中只执行一次
- 多次导入返回相同的实例
- 状态在多个导入者之间共享
对标 C++:
// C++ 防止头文件重复包含
#ifndef MYHEADER_H
#define MYHEADER_H
// ... 内容
#endif
// 或者
#pragma once
// ArkTS 自动保证单例,无需手动处理
4.3 循环依赖
问题:什么是循环依赖?为什么危险?
// 文件:a.ets
import { funcB } from './b';
export function funcA(): void {
console.log("调用 funcA");
funcB();
}
// 文件:b.ets
import { funcA } from './a';
export function funcB(): void {
console.log("调用 funcB");
// funcA(); // 如果调用会导致无限循环
}
循环依赖的危害:
- 编译/加载错误
- 运行时未定义行为
- 难以调试和维护
对标 C++ 头文件循环包含:
// C++ 循环包含问题
// a.h 包含 b.h
// b.h 包含 a.h
// 结果:编译错误
// 解决方案:前向声明
class B; // 前向声明
class A {
B* b; // 使用指针,不依赖完整定义
};
// ArkTS 解决方案:重构代码结构
// 将共享部分提取到第三个模块
// 文件:common.ets
export interface SharedInterface {
doSomething(): void;
}
// 文件:a.ets
import { SharedInterface } from './common';
// 文件:b.ets
import { SharedInterface } from './common';
4.4 动态导入 import()(✅ ArkTS 完全支持)
问题:如果某些模块只在特定条件下才需要,如何避免一开始就全部加载?
4.4.1 为什么需要动态导入
静态导入的局限:
// 静态导入在模块加载时就全部执行
import { HeavyModule } from './heavy-module'; // 无论用不用,都会加载
- 增加启动时间
- 有些模块只在特定条件下才需要
- 大型应用中需要延迟加载非核心功能
C++ 对比:
类似 C++ 中的动态加载共享库:
// Linux
dlopen("./libmylib.so", RTLD_LAZY);
// Windows
LoadLibrary("mylib.dll");
ArkTS 的动态导入 import() 支持加载 HAP/HSP/HAR 模块、ohpm 包、Native 库。
4.4.2 基本语法
// 动态导入返回 Promise
import("./utils").then((module: ESObject) => {
// 使用 module 中的导出
let result: number = module.add(1, 2);
console.log(`结果:${result}`);
});
注意: import() 返回的是一个异步操作结果。后续 Promise 章节会深入讲解,目前只需要知道 .then() 里的代码会在模块加载完成后执行。
4.4.3 使用场景
场景1:条件加载
// 根据运行时条件决定是否加载某个模块
function processData(data: object): void {
if (needsAdvancedProcessing(data)) {
import("./advanced-processor").then((module: ESObject) => {
module.process(data);
});
} else {
// 使用基础处理,不加载高级模块
basicProcess(data);
}
}
场景2:按需加载
// 大型应用中延迟加载非核心功能,提升启动速度
class Application {
private settingsModule: ESObject | null = null;
openSettings(): void {
if (this.settingsModule === null) {
import("./settings-page").then((module: ESObject) => {
this.settingsModule = module;
this.settingsModule.show();
});
} else {
this.settingsModule.show();
}
}
}
4.4.4 与静态导入的对比
| 特性 | 静态 import | 动态 import() |
|---|---|---|
| 加载时机 | 编译时 | 运行时 |
| 位置要求 | 文件顶部 | 任意位置 |
| 返回值 | 直接使用 | Promise |
| 树摇优化 | 支持 | 不支持 |
4.4.5 注意事项
- 动态导入是异步的,不能同步获取结果
- 路径必须是字符串字面量(不能用变量拼接)
- 优先使用静态导入,仅在确实需要时才用动态导入
// ❌ 错误:不能用变量拼接路径
let moduleName: string = "./utils";
import(moduleName); // 编译错误
// ✅ 正确:使用字符串字面量
import("./utils"); // 正确
4.5 re-export(转发导出)
问题:如何创建一个统一的模块出口?
// 文件:utils/math.ets
export function add(a: number, b: number): number { return a + b; }
export function sub(a: number, b: number): number { return a - b; }
// 文件:utils/string.ets
export function trim(s: string): string { return s.trim(); }
export function upper(s: string): string { return s.toUpperCase(); }
// 文件:utils/index.ets
// 统一出口文件
// 转发导出 math 模块的所有导出
export { add, sub } from './math';
// 转发导出 string 模块的所有导出
export { trim, upper } from './string';
// 也可以重命名后转发
export { add as addition } from './math';
// 文件:main.ets
// 只需要导入一个统一出口
import { add, sub, trim, upper } from './utils/index';
console.log(`${add(1, 2)}`);
console.log(`${trim(" hello ")}`);
对标 C++:
// C++ 聚合头文件
// all_headers.h
#include "math.h"
#include "string.h"
#include "vector.h"
// ArkTS 聚合模块
// index.ets
export { ... } from './math';
export { ... } from './string';
export { ... } from './vector';
第五部分:命名空间
5.1 namespace 语法
问题:如何组织相关的类和函数,避免全局命名冲突?
// 文件:geometry.ets
namespace Geometry {
export interface Point {
x: number;
y: number;
}
export class Rectangle {
private x: number;
private y: number;
private width: number;
private height: number;
constructor(x: number, y: number, width: number, height: number) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
area(): number {
return this.width * this.height;
}
}
export class Circle {
private center: Point;
private radius: number;
constructor(center: Point, radius: number) {
this.center = center;
this.radius = radius;
}
area(): number {
return 3.14159 * this.radius * this.radius;
}
}
export function distance(p1: Point, p2: Point): number {
let dx: number = p2.x - p1.x;
let dy: number = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
// 使用命名空间
let rect: Geometry.Rectangle = new Geometry.Rectangle(0, 0, 100, 50);
console.log(`面积:${rect.area()}`);
let p1: Geometry.Point = { x: 0, y: 0 };
let p2: Geometry.Point = { x: 3, y: 4 };
console.log(`距离:${Geometry.distance(p1, p2)}`);
对标 C++ namespace:
// C++
namespace Geometry {
struct Point {
double x, y;
};
class Rectangle {
// ...
};
double distance(Point p1, Point p2) {
// ...
}
}
// 使用
Geometry::Rectangle rect;
Geometry::Point p1;
// ArkTS - 概念完全一致!
Geometry.Rectangle
Geometry.Point
5.2 嵌套命名空间
// 文件:app.ets
namespace App {
export namespace UI {
export class Button {
private label: string;
constructor(label: string) {
this.label = label;
}
render(): string {
return `[Button: ${this.label}]`;
}
}
export class Label {
private text: string;
constructor(text: string) {
this.text = text;
}
render(): string {
return `[Label: ${this.text}]`;
}
}
}
export namespace Data {
export class Store {
private data: Map<string, object> = new Map();
set(key: string, value: object): void {
this.data.set(key, value);
}
get(key: string): object | undefined {
return this.data.get(key);
}
}
}
}
// 使用嵌套命名空间
let btn: App.UI.Button = new App.UI.Button("确定");
console.log(btn.render());
let store: App.Data.Store = new App.Data.Store();
store.set("user", { name: "张三" });
对标 C++ 嵌套 namespace:
// C++17 嵌套命名空间
namespace App::UI {
class Button { };
}
namespace App::Data {
class Store { };
}
// ArkTS 嵌套命名空间
App.UI.Button
App.Data.Store
5.3 命名空间 vs 模块
| 特性 | 模块(Module) | 命名空间(Namespace) |
|---|---|---|
| 边界 | 文件边界 | 代码边界(可在同一文件) |
| 加载 | 按需加载 | 编译时包含 |
| 作用 | 代码组织、复用 | 命名隔离、逻辑分组 |
| 导出 | export 关键字 |
export 关键字 |
| 使用 | import 导入 |
直接访问(同文件)或导入 |
选择建议:
// 场景1:跨文件复用 → 使用模块
// math.ets
export function add() { }
// main.ets
import { add } from './math';
// 场景2:单文件内组织 → 使用命名空间
// geometry.ets
namespace Geometry {
export class Point { }
export class Line { }
}
namespace MathUtils {
export function clamp() { }
}
// 同一文件内使用
let p: Geometry.Point = new Geometry.Point();
// 场景3:大型库 → 模块 + 命名空间结合
// 文件:library.ets
export namespace Core {
export class Application { }
}
export namespace Utils {
export class Logger { }
}
// 其他文件
import { Core, Utils } from './library';
第六部分:小结与练习
6.1 知识点对比总结表
| ArkTS 模块 | C++ 多文件编译 | 说明 |
|---|---|---|
import { x } from './file' |
#include "file.h" |
引入其他文件的声明 |
export |
public: 或全局声明 |
对外暴露接口 |
namespace |
namespace |
命名隔离,概念完全一致 |
| 模块单例 | #pragma once / include guard |
自动防止重复加载 |
export default |
无直接对应 | 单一主要导出 |
as 重命名 |
namespace Alias = Name |
解决命名冲突 |
export { } from '' |
聚合头文件 | 统一出口 |
6.2 使用原则
| 场景 | 推荐方案 |
|---|---|
| 工具函数库 | 命名导出 export function |
| 单一主要类 | 默认导出 export default class |
| 避免命名冲突 | 使用 as 重命名导入 |
| 统一模块出口 | 使用 export { } from '' 转发 |
| 单文件内组织 | 使用 namespace |
| 大型项目结构 | 模块 + 命名空间结合 |
练习题
练习1:基础导出导入
// 1. 创建 math.ets 模块,导出以下函数:
// - sum(a: number, b: number): number
// - average(arr: number[]): number
// - max(a: number, b: number): number
// 2. 在 main.ets 中导入并使用这些函数
// 3. 创建 constants.ets 模块,导出:
// - PI: number = 3.14159
// - E: number = 2.71828
// - MAX_VALUE: number = 1000
点击查看答案
math.ets:
export function sum(a: number, b: number): number {
return a + b;
}
export function average(arr: number[]): number {
let total: number = 0;
for (let i: number = 0; i < arr.length; i = i + 1) {
total = total + arr[i];
}
return total / arr.length;
}
export function max(a: number, b: number): number {
if (a > b) {
return a;
}
return b;
}
constants.ets:
export const PI: number = 3.14159;
export const E: number = 2.71828;
export const MAX_VALUE: number = 1000;
main.ets:
import { sum, average, max } from './math';
import { PI, E, MAX_VALUE } from './constants';
console.log(`sum: ${sum(10, 20)}`);
console.log(`average: ${average([1, 2, 3, 4, 5])}`);
console.log(`max: ${max(10, 20)}`);
console.log(`PI: ${PI}`);
练习2:类与接口导出
// 1. 创建 models.ets 模块,导出:
// - 接口 Animal { name: string; speak(): string }
// - 类 Dog implements Animal
// - 类 Cat implements Animal
// - 枚举 AnimalType { DOG, CAT }
// 2. 在 main.ets 中导入并创建实例
// 3. 使用 as 重命名导入 Animal 为 IAnimal
点击查看答案
models.ets:
export interface Animal {
name: string;
speak(): string;
}
export class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): string {
return `${this.name} says: Woof!`;
}
}
export class Cat implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): string {
return `${this.name} says: Meow!`;
}
}
export enum AnimalType {
DOG,
CAT
}
main.ets:
import { Animal as IAnimal, Dog, Cat, AnimalType } from './models';
let dog: IAnimal = new Dog("Buddy");
let cat: IAnimal = new Cat("Kitty");
console.log(dog.speak());
console.log(cat.speak());
console.log(`Dog type: ${AnimalType.DOG}`);
练习3:命名空间
// 1. 创建 graphics.ets,定义命名空间 Graphics:
// - 接口 Color { r: number; g: number; b: number }
// - 类 Pixel { x: number; y: number; color: Color }
// - 函数 createColor(r, g, b): Color
// 2. 创建嵌套命名空间 Graphics.Filter:
// - 函数 grayscale(color: Color): Color
// - 函数 invert(color: Color): Color
// 3. 在 main.ets 中使用
点击查看答案
graphics.ets:
namespace Graphics {
export interface Color {
r: number;
g: number;
b: number;
}
export class Pixel {
x: number;
y: number;
color: Color;
constructor(x: number, y: number, color: Color) {
this.x = x;
this.y = y;
this.color = color;
}
}
export function createColor(r: number, g: number, b: number): Color {
return { r, g, b };
}
export namespace Filter {
export function grayscale(color: Color): Color {
let avg: number = (color.r + color.g + color.b) / 3;
return { r: avg, g: avg, b: avg };
}
export function invert(color: Color): Color {
return { r: 255 - color.r, g: 255 - color.g, b: 255 - color.b };
}
}
}
export { Graphics };
main.ets:
import { Graphics } from './graphics';
let red: Graphics.Color = Graphics.createColor(255, 0, 0);
let pixel: Graphics.Pixel = new Graphics.Pixel(10, 20, red);
let gray: Graphics.Color = Graphics.Filter.grayscale(red);
let inverted: Graphics.Color = Graphics.Filter.invert(red);
console.log(`Red: ${red.r}, ${red.g}, ${red.b}`);
console.log(`Gray: ${gray.r}, ${gray.g}, ${gray.b}`);
console.log(`Inverted: ${inverted.r}, ${inverted.g}, ${inverted.b}`);
练习4:转发导出
// 1. 创建以下文件结构:
// utils/
// ├── array.ets (导出 find, filter)
// ├── object.ets (导出 clone, merge)
// └── index.ets (转发导出上面所有)
// 2. 在 main.ets 中只导入 utils/index 使用所有功能
点击查看答案
utils/array.ets:
export function find<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
for (let i: number = 0; i < arr.length; i = i + 1) {
if (predicate(arr[i])) {
return arr[i];
}
}
return undefined;
}
export function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
let result: T[] = [];
for (let i: number = 0; i < arr.length; i = i + 1) {
if (predicate(arr[i])) {
result.push(arr[i]);
}
}
return result;
}
utils/object.ets:
export function clone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj)) as T;
}
export function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 } as T & U;
}
utils/index.ets:
export { find, filter } from './array';
export { clone, merge } from './object';
main.ets:
import { find, filter, clone, merge } from './utils/index';
let numbers: number[] = [1, 2, 3, 4, 5];
let found: number | undefined = find(numbers, (n) => n > 3);
let evens: number[] = filter(numbers, (n) => n % 2 === 0);
console.log(`Found: ${found}`);
console.log(`Evens: ${evens}`);
练习5:默认导出
// 1. 创建 Logger.ets,默认导出一个 Logger 类:
// - 方法 log(message: string): void
// - 方法 error(message: string): void
// - 方法 warn(message: string): void
// 2. 在 main.ets 中导入,并命名为 MyLogger
// 3. 创建实例并调用各种方法
点击查看答案
Logger.ets:
export default class Logger {
private prefix: string;
constructor(prefix: string = "[LOG]") {
this.prefix = prefix;
}
log(message: string): void {
console.log(`${this.prefix} ${message}`);
}
error(message: string): void {
console.error(`${this.prefix} [ERROR] ${message}`);
}
warn(message: string): void {
console.warn(`${this.prefix} [WARN] ${message}`);
}
}
main.ets:
import MyLogger from './Logger';
let logger: MyLogger = new MyLogger("[MyApp]");
logger.log("Application started");
logger.warn("Low memory");
logger.error("Connection failed");
练习6:模块单例验证
// 1. 创建 counter.ets:
// - 导出变量 count: number = 0
// - 导出函数 increment(): void
// 2. 创建 moduleA.ets,导入并调用 increment
// 3. 创建 moduleB.ets,导入并调用 increment
// 4. 在 main.ets 中导入 A、B 和 counter,验证 count 的值
点击查看答案
counter.ets:
console.log("Counter module initialized");
export let count: number = 0;
export function increment(): void {
count = count + 1;
console.log(`Count incremented to: ${count}`);
}
moduleA.ets:
import { count, increment } from './counter';
console.log("Module A: before increment");
increment();
console.log(`Module A: count = ${count}`);
moduleB.ets:
import { count, increment } from './counter';
console.log("Module B: before increment");
increment();
console.log(`Module B: count = ${count}`);
main.ets:
import './moduleA';
import './moduleB';
import { count } from './counter';
console.log(`Final count in main: ${count}`);
// 输出:2(证明模块只初始化一次,状态共享)
预期输出:
Counter module initialized
Module A: before increment
Count incremented to: 1
Module A: count = 1
Module B: before increment
Count incremented to: 2
Module B: count = 2
Final count in main: 2
练习7:综合应用
// 实现一个简单的计算器模块系统:
// 1. calculator/
// ├── operations.ets (加减乘除运算)
// ├── history.ets (计算历史记录)
// └── index.ets (统一出口)
// 2. 使用命名空间 Calculator 组织代码
// 3. 默认导出 Calculator 类,支持链式调用:
// calc.add(10).multiply(2).subtract(5).result()
点击查看答案
calculator/operations.ets:
export namespace Calculator {
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
export function divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}
}
calculator/history.ets:
export namespace Calculator {
export class History {
private records: string[] = [];
add(record: string): void {
this.records.push(record);
}
getAll(): string[] {
return this.records;
}
clear(): void {
this.records = [];
}
}
}
calculator/index.ets:
export { Calculator } from './operations';
export { Calculator as CalculatorHistory } from './history';
export default class Calculator {
private value: number = 0;
private history: string[] = [];
add(n: number): Calculator {
this.value = this.value + n;
this.history.push(`add ${n}`);
return this;
}
subtract(n: number): Calculator {
this.value = this.value - n;
this.history.push(`subtract ${n}`);
return this;
}
multiply(n: number): Calculator {
this.value = this.value * n;
this.history.push(`multiply ${n}`);
return this;
}
divide(n: number): Calculator {
if (n === 0) {
throw new Error("Division by zero");
}
this.value = this.value / n;
this.history.push(`divide ${n}`);
return this;
}
result(): number {
return this.value;
}
getHistory(): string[] {
return this.history;
}
}
main.ets:
import Calculator, { Calculator as CalcOps } from './calculator/index';
// 使用默认导出的 Calculator 类(链式调用)
let calc: Calculator = new Calculator();
let result: number = calc.add(10).multiply(2).subtract(5).result();
console.log(`Result: ${result}`); // 15
console.log(`History: ${calc.getHistory()}`);
// 使用命名空间中的函数
let sum: number = CalcOps.add(5, 3);
console.log(`Sum: ${sum}`); // 8
练习8:错误处理与模块
// 1. 创建 errors.ets 模块:
// - 导出类 ValidationError extends Error
// - 导出类 CalculationError extends Error
// - 导出枚举 ErrorCode { INVALID_INPUT, DIVISION_BY_ZERO }
// 2. 创建 calculator.ets 模块,导入 errors 并在适当时候抛出异常
// 3. 在 main.ets 中导入并使用 try/catch 处理
点击查看答案
errors.ets:
export enum ErrorCode {
INVALID_INPUT,
DIVISION_BY_ZERO
}
export class ValidationError extends Error {
code: ErrorCode;
constructor(message: string, code: ErrorCode) {
super(message);
this.code = code;
}
}
export class CalculationError extends Error {
code: ErrorCode;
constructor(message: string, code: ErrorCode) {
super(message);
this.code = code;
}
}
calculator.ets:
import { ValidationError, CalculationError, ErrorCode } from './errors';
export function divide(a: number, b: number): number {
if (b === 0) {
throw new CalculationError("Cannot divide by zero", ErrorCode.DIVISION_BY_ZERO);
}
return a / b;
}
export function validatePositive(n: number): void {
if (n < 0) {
throw new ValidationError("Number must be positive", ErrorCode.INVALID_INPUT);
}
}
main.ets:
import { divide, validatePositive } from './calculator';
import { ValidationError, CalculationError, ErrorCode } from './errors';
try {
validatePositive(-5);
} catch (e) {
if (e instanceof ValidationError) {
console.log(`Validation error: ${e.message}, code: ${e.code}`);
}
}
try {
divide(10, 0);
} catch (e) {
if (e instanceof CalculationError) {
console.log(`Calculation error: ${e.message}, code: ${e.code}`);
}
}
console.log("Program continues...");

浙公网安备 33010602011771号