4、ArkTS语言基础(下):模块化与导入导出、异步编程(Promise与async/await)、装饰器(@Component等)入门、常用内置API

好,咱们接着聊。上一节我们把 ArkTS 的类型系统、变量声明和函数讲透了。这一节,我带你攻克几个真正决定你能否写出“鸿蒙味儿”代码的关键点:模块化、异步编程、装饰器,还有那些高频内置 API。

说实话,这些内容在刚接触时可能会觉得有点绕。但别担心,我当年从 Java 转到 TypeScript 时,也被这些概念折磨过。咱们一个一个来,保证你听完就能上手。

4.1 模块化与导入导出:把代码拆成“积木”

为什么要模块化?说白了,就是别把所有代码塞进一个文件里。你想想看,一个项目几千行代码,全写在一个文件里,找 bug 就像大海捞针。

ArkTS 沿用了 ES6 的模块化标准。核心就两个关键字:exportimport

4.1.1 导出(export)

你可以把模块理解成一个“工具箱”。export 就是把工具从箱子里拿出来,让别人能用。

命名导出

// utils.ets
export function formatDate(date: Date): string {
    return date.toISOString().split('T')[0];
}

export const API_BASE_URL = 'https://api.example.com';

export class User {
    name: string;
    age: number;
}

默认导出

// Logger.ets
export default class Logger {
    static log(msg: string) {
        console.info('[LOG]: ' + msg);
    }
}
我的习惯:一个文件尽量只做一个默认导出。这样别人一看就知道这个文件的核心功能是什么。我在项目中吃过亏,一个文件导出好几个默认,结果自己都搞混了。

4.1.2 导入(import)

有导出就有导入。就像你从工具箱里拿工具出来用。

// main.ets
import { formatDate, API_BASE_URL } from './utils';
import Logger from './Logger';

let today = formatDate(new Date());
Logger.log('今天日期:' + today);

重命名导入

import { formatDate as fd } from './utils';
注意:路径一定要写对。我曾经因为少写了一个 ./,排查了半小时。在鸿蒙项目中,相对路径必须以 ./../ 开头。

4.2 异步编程:Promise 与 async/await

鸿蒙开发中,网络请求、文件读写、数据库操作,这些都是异步的。说白了,就是你不能让 App 卡在那里等结果。

ArkTS 的异步方案和现代 JavaScript 完全一致:Promiseasync/await

4.2.1 Promise 基础

Promise 就像一个“承诺”。它有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。

function fetchUserData(userId: string): Promise<User> {
    return new Promise((resolve, reject) => {
        // 模拟网络请求
        setTimeout(() => {
            if (userId) {
                resolve({ name: '张三', age: 25 });
            } else {
                reject(new Error('用户ID不能为空'));
            }
        }, 1000);
    });
}

// 使用 Promise
fetchUserData('123')
    .then(user => {
        console.info('用户数据:' + JSON.stringify(user));
    })
    .catch(error => {
        console.error('请求失败:' + error.message);
    });

避坑指南:我曾经在项目中忘了写 .catch(),结果 Promise 抛出的错误直接导致应用闪退。记住:每个 Promise 都必须有错误处理

4.2.2 async/await:更优雅的写法

说实话,.then() 链式调用写多了,代码会变得很难看。尤其是多个异步操作有依赖关系时,回调地狱就来了。

async/await 就是来解决这个问题的。它让异步代码看起来像同步代码一样直观。

async function loadUserProfile(userId: string): Promise<void> {
    try {
        let user = await fetchUserData(userId);
        console.info('用户信息加载完成');
        
        // 可以继续 await 其他异步操作
        let orders = await fetchUserOrders(user.id);
        console.info('订单数量:' + orders.length);
    } catch (error) {
        console.error('加载失败:' + error.message);
    }
}

嗯,这里要注意:await 只能在 async 函数内部使用。这个规则我刚开始也经常忘。

4.3 装饰器入门:@Component 等

装饰器是 ArkTS 的一大特色。说白了,它就是一种特殊的声明,可以附加在类、方法、属性上,用来修改它们的行为。

在鸿蒙开发中,你用得最多的就是 @Component@State@Prop 这些。

4.3.1 @Component:声明一个自定义组件

@Component
struct MyButton {
    build() {
        Button('点击我')
            .onClick(() => {
                console.info('按钮被点击了');
            });
    }
}

看到 @Component 了吗?它告诉编译器:这是一个 UI 组件。你想想看,如果没有这个装饰器,编译器怎么知道一个结构体是普通数据还是 UI 组件?

4.3.2 @State:让数据驱动 UI

@Component
struct Counter {
    @State count: number = 0;

    build() {
        Column() {
            Text('计数:' + this.count)
                .fontSize(20);
            Button('增加')
                .onClick(() => {
                    this.count++;
                });
        }
    }
}
我的经验:@State 装饰的变量发生变化时,UI 会自动刷新。这就是“数据驱动 UI”的核心思想。我在做电商项目时,购物车数量就是通过 @State 实现的,用户体验非常好。

4.3.3 @Prop:父子组件传值

@Component
struct ChildComponent {
    @Prop message: string;

    build() {
        Text(this.message);
    }
}

@Component
struct ParentComponent {
    build() {
        ChildComponent({ message: '来自父组件的数据' });
    }
}

4.4 常用内置 API

ArkTS 提供了一些非常实用的内置 API。我挑几个高频的给你讲讲。

4.4.1 控制台输出

console.info('这是一条信息');
console.warn('这是一条警告');
console.error('这是一条错误');

调试时,console 是你的好帮手。我个人习惯在关键逻辑处加 console.info,方便追踪执行流程。

4.4.2 定时器

// 延迟执行
setTimeout(() => {
    console.info('1秒后执行');
}, 1000);

// 定时执行
let intervalId = setInterval(() => {
    console.info('每秒执行一次');
}, 1000);

// 清除定时器
clearInterval(intervalId);
注意:定时器用完后一定要清除。我曾经在页面跳转时忘了清除定时器,结果导致内存泄漏,App 越来越卡。

4.4.3 JSON 处理

let user = { name: '李四', age: 30 };
let jsonStr = JSON.stringify(user);
console.info(jsonStr); // {"name":"李四","age":30}

let parsedUser = JSON.parse(jsonStr);
console.info(parsedUser.name); // 李四

4.4.4 数学计算

let max = Math.max(10, 20, 30); // 30
let min = Math.min(10, 20, 30); // 10
let random = Math.random(); // 0到1之间的随机数
let rounded = Math.round(3.7); // 4

4.5 综合示例:一个完整的异步数据加载组件

最后,我把今天讲的内容串起来,给你看一个完整的例子。

// UserService.ets
export class UserService {
    static async fetchUser(id: string): Promise<User> {
        // 模拟网络请求
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve({ id: id, name: '王五', age: 28 });
            }, 500);
        });
    }
}

// UserProfile.ets
import { UserService } from './UserService';

@Component
struct UserProfile {
    @State userName: string = '加载中...';
    @State userAge: number = 0;

    async aboutToAppear() {
        try {
            let user = await UserService.fetchUser('001');
            this.userName = user.name;
            this.userAge = user.age;
        } catch (error) {
            console.error('加载用户数据失败:' + error.message);
        }
    }

    build() {
        Column() {
            Text('用户名:' + this.userName)
                .fontSize(18);
            Text('年龄:' + this.userAge)
                .fontSize(18);
        }
        .padding(20);
    }
}

看到没?模块化导出、异步加载、装饰器驱动 UI,全用上了。这就是鸿蒙端云一体化开发的基础套路。

好了,这一节的内容就到这里。下一节我们开始真正搭建项目结构,到时候这些知识你都会用得上。