Skip to content

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.14159
typescript
// pages/index.ts
// 导入多个命名导出
import { add, subtract } from '../utils/math'
import { PI } from '../utils/math'

console.log(add(1, 2))  // 3
console.log(PI)         // 3.14159

2.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 如何避免

  1. 抽离公共模块:将 A 和 B 都依赖的部分抽到 C.ets
  2. 延迟导入:在函数内部 import
  3. 重构架构:检查模块职责是否过于耦合
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 = 5000
typescript
// 只有首次导入时执行顶层代码
import { API_URL } from './config'  // 输出 "config 模块加载"
import { TIMEOUT } from './config'  // 不输出(已加载过)

7. 面试高频考点

Q1: ArkTS 模块加载机制的特点?

回答:每个模块只加载一次(单例),多次导入返回同一实例。导出时直接导出实例对象,天然实现单例模式,比手动管理更安全可靠。

Q2: 命名导出 vs 默认导出?

回答

  • 命名导出:一个文件可以有多个,导入时要用 {} 解构
  • 默认导出:一个文件只能有一个,导入时可以任意命名
  • 推荐:工具类/组件用默认导出,配置/常量用命名导出

Q3: 如何解决循环依赖?

回答:1. 抽离公共模块;2. 延迟导入(函数内 import);3. 重构架构解耦。最推荐的是方案 1。


🐱 小猫提示:模块系统的核心就一句话——"文件即模块,加载一次,导出即共享"。记住模块级导出实现单例的技巧,面试中非常加分。