Appearance
ArkTS 模块系统
ArkTS 的模块加载机制、导出导入语法、模块单例模式,以及模块化架构的基础。
1. 模块概述
ArkTS 使用 ES Module 规范,每个 .ets 文件是一个模块。模块之间通过 import/export 进行通信。
核心特性
- ES Module 规范:
import/export default/export - 模块加载是单例的:每个模块只加载一次,多次导入返回同一实例
- 文件即模块:每个
.ets文件是一个独立的模块作用域 - 支持路径别名:配置
ts.json后可使用相对路径或别名导入
2. 模块导出
2.1 命名导出(Named Export)
typescript
// utils/math.ts
export function add(a: number, b: number): number {
return a + b
}
export function subtract(a: number, b: number): number {
return a - b
}
export const PI: number = 3.14159typescript
// pages/index.ts
// 导入多个命名导出
import { add, subtract } from '../utils/math'
import { PI } from '../utils/math'
console.log(add(1, 2)) // 3
console.log(PI) // 3.141592.2 默认导出(Default Export)
typescript
// utils/HttpClient.ts
export default class HttpClient {
private baseUrl: string
constructor(baseUrl: string) {
this.baseUrl = baseUrl
}
get<T>(path: string): Promise<T> {
// ...
return Promise.resolve(null as unknown as T)
}
post<T>(path: string, data: any): Promise<T> {
// ...
return Promise.resolve(null as unknown as T)
}
}typescript
// pages/index.ts
// 导入默认导出(可以任意命名)
import HttpClient from '../utils/HttpClient'
const client = new HttpClient('https://api.example.com')2.3 混合导出
typescript
// utils/index.ts
// 默认导出
export default class Utils {
static formatDate(date: Date): string {
return date.toISOString()
}
}
// 命名导出
export const VERSION = '1.0.0'
export function isEmpty(value: any): boolean {
return value === null || value === undefined || value === ''
}typescript
// pages/index.ts
import Utils, { VERSION, isEmpty } from '../utils/index'
console.log(VERSION)
console.log(isEmpty(''))3. 模块单例模式
3.1 推荐方式:模块级导出
typescript
// services/ApiService.ets
class ApiService {
private static instance: ApiService | null = null
private baseUrl: string = 'https://api.example.com'
private constructor() {}
static getInstance(): ApiService {
if (!ApiService.instance) {
ApiService.instance = new ApiService()
}
return ApiService.instance
}
async get<T>(url: string): Promise<T> {
// ...
}
}
// ✅ 推荐:直接导出单例实例
// 因为模块加载是单例的,此实例全局唯一
export default ApiService.getInstance()typescript
// pages/index.ts
import apiService from '../services/ApiService'
// 全局唯一实例
apiService.get<User>('/user/1')3.2 为什么不推荐 class 单例?
typescript
// ❌ 不推荐:手动管理单例容易出错
class Service {
static instance: Service | null = null
static getInstance(): Service {
if (!this.instance) this.instance = new Service()
return this.instance
}
}
// ✅ 推荐:模块级导出(简洁、安全、无 bug)
// 模块加载器保证只加载一次
export default new Service()4. 模块导入路径
4.1 相对路径
typescript
// 当前目录
import { helper } from './utils'
// 父目录
import { helper } from '../utils'
// 子目录
import { helper } from './components/Button'4.2 路径别名(tsconfig 配置)
json
// hvigor/hvigorfile.ts 或 tsconfig.json
{
"compilerOptions": {
"paths": {
"@utils/*": ["utils/*"],
"@services/*": ["services/*"],
"@components/*": ["components/*"]
}
}
}typescript
// 使用别名导入
import { helper } from '@utils/helper'
import ApiService from '@services/ApiService'5. 循环依赖
5.1 什么是循环依赖
typescript
// A.ets
import { bFunc } from './B' // A 依赖 B
export function aFunc() { ... }
// B.ets
import { aFunc } from './A' // B 依赖 A ← 循环!
export function bFunc() { ... }5.2 如何避免
- 抽离公共模块:将 A 和 B 都依赖的部分抽到 C.ets
- 延迟导入:在函数内部 import
- 重构架构:检查模块职责是否过于耦合
typescript
// 方案2:延迟导入(不推荐长期使用,但可应急)
export function aFunc() {
// 延迟加载 B,打破循环
const { bFunc } = require('./B')
return bFunc()
}6. 模块加载机制
6.1 加载顺序
1. 遇到 import 语句
2. 编译器解析模块路径
3. 加载模块文件(只加载一次)
4. 执行模块顶层代码
5. 导出值
6. 导入方获取导出的值6.2 模块顶层代码执行
typescript
// config.ets
console.log('config 模块加载') // 模块首次加载时执行
export const API_URL = 'https://api.example.com'
export const TIMEOUT = 5000typescript
// 只有首次导入时执行顶层代码
import { API_URL } from './config' // 输出 "config 模块加载"
import { TIMEOUT } from './config' // 不输出(已加载过)7. 面试高频考点
Q1: ArkTS 模块加载机制的特点?
回答:每个模块只加载一次(单例),多次导入返回同一实例。导出时直接导出实例对象,天然实现单例模式,比手动管理更安全可靠。
Q2: 命名导出 vs 默认导出?
回答:
- 命名导出:一个文件可以有多个,导入时要用
{}解构 - 默认导出:一个文件只能有一个,导入时可以任意命名
- 推荐:工具类/组件用默认导出,配置/常量用命名导出
Q3: 如何解决循环依赖?
回答:1. 抽离公共模块;2. 延迟导入(函数内 import);3. 重构架构解耦。最推荐的是方案 1。
🐱 小猫提示:模块系统的核心就一句话——"文件即模块,加载一次,导出即共享"。记住模块级导出实现单例的技巧,面试中非常加分。