ArkTS语言核心特性-装饰器与类型系统
ArkTS 语言核心特性摘要 ArkTS 是 HarmonyOS 优化的声明式 UI 语言,基于 TypeScript 但具备以下关键特性: 核心差异: 装饰器系统为核心特性(非实验性) 内置 build() 方法构建 UI 专门的状态管理装饰器(@State、@ObservedV2) 特有的资源访问语法 $r() 装饰器系统: @ComponentV2 定义组件 @Entry 标记页面入口 @L
ArkTS 语言核心特性完全指南 - 装饰器、类型系统、异步编程深度解析
系列文章:HarmonyOS 6.0 实战开发 - 「今天空白」应用
第3篇 / 共30篇
发布时间:2026-02-09
阅读时长:20分钟
难度:(进阶)
本文导读
在上一篇中,我们成功搭建了 HarmonyOS 6.0 开发环境。从今天开始,我们深入 ArkTS 语言本身。
代码说明
- 本文所有代码示例均来自实际运行的项目
- 完整源码:GitHub - Today_is_blank
- 建议对照源码阅读,理解完整的项目结构和设计思路
ArkTS 是什么?
- TypeScript 的超集(TypeScript 代码可以直接运行)
- 针对 HarmonyOS 优化的声明式UI语言
- 支持装饰器系统的现代编程语言
本文内容:
- ArkTS vs TypeScript:关键差异
- 装饰器系统完全解析
- 类型系统与类型推导
- 异步编程:Promise、async/await
- 模块化与依赖管理
- 真实项目代码剖析
学完本文你将:
- 理解 ArkTS 的设计哲学
- 掌握装饰器的正确使用方式
- 能够编写类型安全的 ArkTS 代码
- 理解「今天空白」项目的核心代码
ArkTS vs TypeScript
相同点
ArkTS 完全兼容 TypeScript 语法:
// 这些 TypeScript 代码在 ArkTS 中都能运行
// 1. 类型注解
let name: string = '今天空白';
let age: number = 25;
let isActive: boolean = true;
// 2. 接口
interface User {
id: number;
name: string;
email?: string; // 可选属性
}
// 3. 泛型
function identity<T>(arg: T): T {
return arg;
}
// 4. 类
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
greet(): string {
return `Hello, ${this.name}`;
}
}
// 5. 箭头函数
const add = (a: number, b: number): number => a + b;
// 6. 解构
const { name: userName, age: userAge } = user;
关键差异
| 特性 | TypeScript | ArkTS | 说明 |
|---|---|---|---|
| 装饰器 | 实验性特性(需开启) | 核心特性 | ArkUI 的基础 |
| UI 构建 | 无 | build() 方法 | 声明式 UI |
| 状态管理 | 需第三方库 | 内置装饰器 | @State、@ObservedV2 等 |
| 资源访问 | 普通字符串 | $r() 语法 | 访问资源文件 |
| 运行环境 | Node.js/浏览器 | ArkTS Runtime | HarmonyOS 专用 |
装饰器系统:ArkTS 的灵魂
装饰器是 ArkTS 最重要的特性,用于:
- 定义组件
- 管理状态
- 控制生命周期
- 注入依赖
1. 组件装饰器
@ComponentV2 - 新一代组件
@ComponentV2
export struct TodayCard {
// 组件内容
build() {
Column() {
Text('今天空白')
}
}
}
关键点:
@ComponentV2是 V2 状态管理的基础- 必须有
build()方法 export让组件可被其他文件引用
@Entry - 页面入口
@Entry
@ComponentV2
struct Index {
build() {
Column() {
Text('这是首页')
}
}
}
关键点:
@Entry标记页面入口- 一个页面只能有一个
@Entry组件 - 通常在
pages/目录下
2. 状态管理装饰器(V2)
@Local - 组件内部状态
@ComponentV2
struct Counter {
// 组件私有状态
@Local count: number = 0;
build() {
Column() {
Text(`点击次数:${this.count}`)
Button('点击')
.onClick(() => {
this.count++; // 修改会触发 UI 更新
})
}
}
}
关键点:
@Local用于组件内部状态- 状态变化会自动更新 UI
- 不能传递给子组件
@Param - 父子组件通信
// 子组件
@ComponentV2
export struct UserCard {
// 接收父组件传入的参数
@Param userName: string = '';
@Param @Require age!: number; // 必传参数
build() {
Column() {
Text(this.userName)
Text(`${this.age}岁`)
}
}
}
// 父组件
@ComponentV2
struct Parent {
@Local name: string = '张三';
@Local age: number = 25;
build() {
Column() {
// 传递参数给子组件
UserCard({ userName: this.name, age: this.age })
}
}
}
关键点:
@Param接收父组件传入的值@Require标记必传参数- 父组件修改,子组件自动更新
@ObservedV2 - 响应式数据类
// 定义响应式数据模型
@ObservedV2
export class UserInfo {
@Trace name: string = '';
@Trace age: number = 0;
}
// 在组件中使用
@ComponentV2
struct Profile {
@Local user: UserInfo = new UserInfo();
build() {
Column() {
Text(this.user.name) // 自动追踪变化
Text(`${this.user.age}岁`)
Button('修改姓名')
.onClick(() => {
this.user.name = '李四'; // UI 自动更新
})
}
}
}
关键点:
@ObservedV2让类变为响应式@Trace标记需要追踪的属性- 修改属性会自动触发 UI 更新
3. 真实项目示例:今天空白
让我们看看「今天空白」项目中的真实代码:
代码说明: 以下代码来自实际运行的项目,完整代码请查看 GitHub 仓库
DailyEntry.ets - 领域模型
// 每日记录数据模型
export interface DailyEntry {
date: string; // YYYY-MM-DD
text: string; // 记录内容
createdAt: number; // 创建时间戳
updatedAt: number; // 更新时间戳
}
// 文本标准化处理
export function normalizeText(text: string): string {
return text.trim();
}
// 检查文本是否为空
export function isBlankText(text: string): boolean {
return normalizeText(text).length === 0;
}
// 创建新记录
export function createEntry(dateKey: string, text: string, nowMs: number): DailyEntry {
const normalized = normalizeText(text);
const entry: DailyEntry = {
date: dateKey,
text: normalized,
createdAt: nowMs,
updatedAt: nowMs
};
return entry;
}
// 更新现有记录
export function updateEntry(prev: DailyEntry, text: string, nowMs: number): DailyEntry {
const normalized = normalizeText(text);
const entry: DailyEntry = {
date: prev.date,
text: normalized,
createdAt: prev.createdAt, // 保持原创建时间
updatedAt: nowMs // 更新修改时间
};
return entry;
}
关键设计要点:
- 使用
interface定义数据结构,不使用装饰器(纯数据模型) - 导出独立的工具函数,符合函数式编程理念
createEntry和updateEntry分离,语义清晰- 时间戳由调用方传入,便于测试和时间控制
TodayStore.ets - 状态管理
// Usage: const store = new TodayStore(repo); await store.refresh()
import { todayKey } from '../../common/time/DateKey';
import { createEntry, isBlankText, normalizeText, updateEntry } from './DailyEntry';
import { TodayRepo } from './TodayRepo';
export type TodayStatus = 'loading' | 'blank' | 'recorded' | 'editing' | 'saving';
@ObservedV2
export class TodayStore {
// 响应式状态 - 使用 @Trace 标记
@Trace status: TodayStatus = 'loading';
@Trace dateKey: string = '';
@Trace text: string = '';
@Trace draftText: string = '';
@Trace updatedAt: number = 0;
@Trace errorMessage: string = '';
// 私有字段 - 不需要响应式
private repo: TodayRepo;
private lastStableStatus: 'blank' | 'recorded' = 'blank';
constructor(repo: TodayRepo) {
this.repo = repo;
}
// 刷新今日数据
async refresh(now: Date = new Date()): Promise<void> {
this.errorMessage = '';
this.status = 'loading';
const key = todayKey(now); // 使用外部工具函数获取日期键
this.dateKey = key;
const entry = await this.repo.get(key);
if (!entry) {
// 没有记录 - 今天空白
this.text = '';
this.updatedAt = 0;
this.status = 'blank';
this.lastStableStatus = 'blank';
return;
}
// 有记录 - 已记录
this.text = entry.text;
this.updatedAt = entry.updatedAt;
this.status = 'recorded';
this.lastStableStatus = 'recorded';
}
// 打开编辑器
openEditor(): void {
this.errorMessage = '';
this.draftText = this.text; // 复制当前文本到草稿
this.status = 'editing';
}
// 取消编辑
cancelEdit(): void {
this.errorMessage = '';
this.draftText = '';
this.status = this.lastStableStatus; // 恢复之前的稳定状态
}
// 保存记录
async save(nextText: string): Promise<void> {
const normalized = normalizeText(nextText);
if (isBlankText(normalized)) {
this.errorMessage = '内容不能为空';
return;
}
this.status = 'saving';
this.errorMessage = '';
const nowMs = Date.now();
try {
const existing = await this.repo.get(this.dateKey);
// 根据是否存在选择创建或更新
const entry = existing
? updateEntry(existing, normalized, nowMs)
: createEntry(this.dateKey, normalized, nowMs);
await this.repo.set(entry);
// 更新成功
this.text = entry.text;
this.updatedAt = entry.updatedAt;
this.draftText = '';
this.status = 'recorded';
this.lastStableStatus = 'recorded';
} catch (_err) {
this.errorMessage = '保存失败';
this.status = 'editing';
}
}
// 清空记录
async clear(): Promise<void> {
this.status = 'saving';
this.errorMessage = '';
try {
await this.repo.remove(this.dateKey);
// 清空成功
this.text = '';
this.updatedAt = 0;
this.draftText = '';
this.status = 'blank';
this.lastStableStatus = 'blank';
} catch (_err) {
this.errorMessage = '保存失败';
this.status = this.lastStableStatus;
}
}
}
关键设计要点:
@ObservedV2将类变为响应式,配合@Trace实现精确更新- 使用
type定义状态枚举,提供类型安全 - 区分稳定状态 (
blank/recorded) 和过渡状态 (loading/editing/saving) lastStableStatus用于取消编辑时恢复状态- 使用外部工具函数 (
todayKey、createEntry、updateEntry) 保持职责单一 - Repository 模式隔离数据访问逻辑
TodayPage.ets - UI 组件
注意: 以下展示核心结构,实际项目包含完整的 UI 实现(背景图、设置按钮、清空确认对话框等),完整代码约 209 行,请查看 GitHub - TodayPage.ets
// Usage: TodayPage({ store, onSave, onClear, onOpenSettings })
import { TodayStore } from './TodayStore';
import { EditSheet } from './EditSheet';
import { UiTokens } from '../ui/UiTokens';
@ComponentV2
export struct TodayPage {
// 必传参数 - 使用 @Require + @Param
@Require
@Param store!: TodayStore;
@Require
@Param onSave!: (text: string) => void;
@Require
@Param onClear!: () => void;
@Require
@Param onOpenSettings!: () => void;
// 组件内部状态
@Local showClearConfirm: boolean = false;
// 计算属性(通过方法实现)
private showBlank(): boolean {
return this.store.status === 'blank' || this.store.status === 'loading';
}
build() {
Stack() {
// 背景层
Column() {}
.width('100%')
.height('100%')
.backgroundColor(UiTokens.ColorPageBg);
Image($r('app.media.background'))
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.opacity(0.18);
// 主内容区
Column() {
// 头部:日期 + 设置按钮
Row() {
Column() {
Text('今天')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor(UiTokens.ColorText);
Text(this.store.dateKey || '')
.fontSize(12)
.fontColor(UiTokens.ColorTextMuted)
.margin({ top: 2 });
}
Button('设置')
.onClick(() => this.onOpenSettings())
.height(UiTokens.ButtonHeight);
}
.justifyContent(FlexAlign.SpaceBetween)
.width('100%');
// 内容卡片
Column() {
if (this.showBlank()) {
// 空白态
Text('今天空白')
.fontSize(30)
.fontColor(UiTokens.ColorText);
} else {
// 已记录态
Text(this.store.text)
.fontSize(20)
.fontColor(UiTokens.ColorText)
.lineHeight(28);
}
// 错误提示
if (this.store.errorMessage && this.store.status !== 'editing') {
Text(this.store.errorMessage)
.fontSize(12)
.fontColor(UiTokens.ColorDanger);
}
}
.backgroundColor(UiTokens.ColorSurface)
.borderRadius(UiTokens.RadiusL)
.width('100%');
// 底部按钮区
if (this.showBlank()) {
// 空白态:显示"记录一件事"按钮
Button('记录一件事')
.onClick(() => this.store.openEditor())
.backgroundColor(UiTokens.ColorPrimary)
.fontColor(UiTokens.ColorPrimaryText)
.width('100%');
} else {
// 已记录态:显示"修改"和"清空"按钮
Row() {
Button('修改')
.onClick(() => this.store.openEditor())
.backgroundColor(UiTokens.ColorPrimary)
.layoutWeight(1);
Button('清空')
.onClick(() => {
this.showClearConfirm = true;
})
.backgroundColor(UiTokens.ColorSurfaceSoft)
.layoutWeight(1);
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween);
}
}
.padding(UiTokens.PagePadding)
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceBetween);
// 编辑弹层
if (this.store.status === 'editing') {
Stack() {
// 半透明遮罩
Column() {}
.width('100%')
.height('100%')
.backgroundColor(UiTokens.ColorPrimary)
.opacity(0.35);
// 编辑表单组件
EditSheet({
store: this.store,
onSave: (text: string) => this.onSave(text)
});
}
.width('100%')
.height('100%');
}
// 清空确认对话框(实际代码中还有完整实现,此处省略)
// if (this.showClearConfirm) { ... }
}
.width('100%')
.height('100%');
}
}
关键设计要点:
@Require+@Param组合确保父组件必须传递参数,编译时校验@Local管理组件内部状态(showClearConfirm)- 使用方法实现计算属性(
showBlank()),保持逻辑清晰 - 条件渲染根据状态切换 UI(
if/else) - 使用
UiTokens统一管理设计令牌(颜色、尺寸、间距) - Stack 布局实现背景图 + 内容的层叠效果
- 组件间通过回调函数通信(
onSave、onClear、onOpenSettings) - 职责清晰:页面组件只负责 UI 渲染和事件分发,业务逻辑在 Store 中
类型系统
基础类型
// 1. 基本类型
let str: string = 'Hello';
let num: number = 42;
let bool: boolean = true;
let undef: undefined = undefined;
let nul: null = null;
// 2. 数组
let arr1: number[] = [1, 2, 3];
let arr2: Array<string> = ['a', 'b', 'c'];
// 3. 元组
let tuple: [string, number] = ['今天空白', 1];
// 4. 枚举
enum Status {
Loading = 'loading',
Blank = 'blank',
Recorded = 'recorded'
}
let status: Status = Status.Blank;
// 5. Any(尽量避免)
let anything: any = '可以是任何类型';
接口与类型别名
// 接口(推荐用于对象)
interface Entry {
date: string;
text: string;
readonly id: number; // 只读属性
tags?: string[]; // 可选属性
}
// 类型别名(推荐用于联合类型)
type Status = 'loading' | 'blank' | 'recorded';
type ID = number | string;
// 类型别名也可以用于对象(但接口更灵活)
type Point = {
x: number;
y: number;
};
联合类型与交叉类型
// 联合类型(或)
type Result = string | number;
let result: Result = '成功';
result = 200; //
// 交叉类型(且)
interface Nameable {
name: string;
}
interface Ageable {
age: number;
}
type Person = Nameable & Ageable;
let person: Person = {
name: '张三',
age: 25
};
泛型
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>('Hello'); // 明确指定类型
let output2 = identity(123); // 类型推导
// 泛型接口
interface Repository<T> {
save(item: T): Promise<void>;
load(id: string): Promise<T | null>;
delete(id: string): Promise<void>;
}
// 实现泛型接口
class EntryRepository implements Repository<DailyEntry> {
async save(item: DailyEntry): Promise<void> {
// 保存逻辑
}
async load(id: string): Promise<DailyEntry | null> {
// 加载逻辑
return null;
}
async delete(id: string): Promise<void> {
// 删除逻辑
}
}
类型推导与类型断言
// 类型推导(TypeScript 自动推断)
let message = 'Hello'; // 推导为 string
let count = 42; // 推导为 number
// 类型断言(手动指定类型)
let someValue: any = 'this is a string';
// 方式1:<> 语法(不推荐,在 JSX/TSX 中冲突)
let strLength1 = (<string>someValue).length;
// 方式2:as 语法(推荐)
let strLength2 = (someValue as string).length;
异步编程
HarmonyOS 中的大部分操作都是异步的:
- 网络请求
- 文件读写
- 数据库操作
Promise 基础
// 创建 Promise
function delay(ms: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
// 使用 Promise
delay(1000).then(() => {
console.log('1秒后执行');
});
// 链式调用
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => console.log(posts))
.catch(error => console.error(error));
async/await(推荐)
// 异步函数
async function loadData(): Promise<void> {
try {
// await 等待 Promise 完成
const user = await fetchUser();
const posts = await fetchPosts(user.id);
console.log(posts);
} catch (error) {
console.error('加载失败', error);
}
}
// 真实示例:TodayStore.init()
async init(): Promise<void> {
this.status = 'loading';
try {
// await 等待数据库读取完成
const entry = await this.repo.loadToday(this.dateKey);
if (entry && entry.text.trim().length > 0) {
this.text = entry.text;
this.status = 'recorded';
} else {
this.status = 'blank';
}
} catch (error) {
this.errorMessage = '加载失败';
this.status = 'blank';
}
}
并发执行
// 串行执行(慢)
async function sequential() {
const user = await fetchUser(); // 等待 1 秒
const posts = await fetchPosts(); // 等待 2 秒
// 总计:3 秒
}
// 并行执行(快)
async function parallel() {
const [user, posts] = await Promise.all([
fetchUser(), // 同时开始
fetchPosts() // 同时开始
]);
// 总计:2 秒(最慢的那个)
}
// 真实示例:批量加载
async function loadMultipleDays(): Promise<DailyEntry[]> {
const dateKeys = ['2026-01-20', '2026-01-21', '2026-01-22'];
// 并行加载
const entries = await Promise.all(
dateKeys.map(key => repo.loadToday(key))
);
// 过滤掉 null
return entries.filter(e => e !== null) as DailyEntry[];
}
模块化
导出
// 导出接口
export interface DailyEntry {
date: string;
text: string;
}
// 导出类
export class TodayStore {
// ...
}
// 导出函数
export function formatDate(date: Date): string {
return date.toISOString();
}
// 导出常量
export const API_URL = 'https://api.example.com';
// 默认导出(一个文件只能有一个)
export default class App {
// ...
}
导入
// 导入具名导出
import { DailyEntry, TodayStore } from './TodayStore';
// 导入默认导出
import App from './App';
// 导入所有(as 语法)
import * as Utils from './Utils';
Utils.formatDate(new Date());
// 导入并重命名
import { TodayStore as Store } from './TodayStore';
命名空间
// 定义命名空间
export namespace DailyEntry {
export function create(date: string, text: string): DailyEntry {
// ...
}
export function validate(entry: DailyEntry): boolean {
// ...
}
}
// 使用命名空间
import { DailyEntry } from './DailyEntry';
const entry = DailyEntry.create('2026-01-20', '今天完成了...');
const isValid = DailyEntry.validate(entry);
资源访问
ArkTS 提供了 $r() 语法访问资源文件:
// 访问字符串资源
Text($r('app.string.app_name'))
// 访问图片资源
Image($r('app.media.background'))
// 访问颜色资源
.backgroundColor($r('app.color.primary'))
// 资源文件位置
// entry/src/main/resources/base/element/string.json
{
"string": [
{
"name": "app_name",
"value": "今天空白"
}
]
}
最佳实践
1. 优先使用 const
// 推荐
const name = '今天空白';
// 避免
let name = '今天空白'; // 如果不需要重新赋值
2. 明确类型注解
// 推荐(清晰)
function add(a: number, b: number): number {
return a + b;
}
// 避免(依赖推导)
function add(a, b) { // 类型为 any
return a + b;
}
3. 使用可选链
// 推荐
const userName = user?.profile?.name ?? '未知';
// 避免
const userName = user && user.profile && user.profile.name
? user.profile.name
: '未知';
4. 避免使用 any
// 避免
let data: any = fetchData();
// 推荐
interface ApiResponse {
code: number;
data: DailyEntry[];
}
let response: ApiResponse = await fetchData();
5. 合理使用装饰器
// 推荐:Store 类使用 @ObservedV2
@ObservedV2
export class TodayStore {
@Trace status: string = 'loading';
}
// 避免:普通数据类不需要装饰器
interface DailyEntry { // 不需要 @ObservedV2
date: string;
text: string;
}
实战练习
练习 1:创建计数器组件
要求:
- 使用
@ComponentV2 - 使用
@Local管理计数 - 点击按钮增加计数
@ComponentV2
export struct Counter {
@Local count: number = 0;
build() {
Column({ space: 20 }) {
Text(`计数:${this.count}`)
.fontSize(24)
Button('增加')
.onClick(() => {
this.count++;
})
}
.padding(20)
}
}
练习 2:创建用户信息 Store
要求:
- 使用
@ObservedV2 - 包含 name 和 age 属性
- 提供 updateName 和 updateAge 方法
@ObservedV2
export class UserStore {
@Trace name: string = '';
@Trace age: number = 0;
updateName(newName: string): void {
this.name = newName;
}
updateAge(newAge: number): void {
this.age = newAge;
}
}
练习 3:异步加载数据
要求:
- 模拟延迟 1 秒
- 使用 async/await
- 处理错误
function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function loadUserData(): Promise<User | null> {
try {
await delay(1000); // 模拟网络请求
// 模拟返回数据
return {
id: 1,
name: '张三',
age: 25
};
} catch (error) {
console.error('加载失败', error);
return null;
}
}
学习检查清单
完成本文后,你应该能够:
- 理解 ArkTS 与 TypeScript 的关系
- 掌握 @ComponentV2、@Local、@Param 等装饰器
- 理解 @ObservedV2 和 @Trace 的作用
- 能够定义接口和类型
- 熟练使用 async/await 处理异步操作
- 理解模块导入导出机制
- 能够阅读「今天空白」项目代码
扩展学习
推荐文档
进阶主题
- 装饰器的实现原理
- 类型体操(高级类型)
- 依赖注入(IoC)
下期预告
下一篇:《V2 状态管理完全指南(上)- 核心概念与基础用法》
内容包括:
- V1 vs V2:为什么必须升级
- @ObservedV2 深度解析
- @Trace 依赖追踪原理
- Store 模式设计
- 性能对比与优化
发布时间:2026-02-09
互动环节
今日思考题
- 你之前使用过 TypeScript 吗?感觉 ArkTS 有什么不同?
- 你最喜欢 ArkTS 的哪个特性?为什么?
- 对装饰器的理解还有疑问吗?
欢迎在评论区分享!
如果本文对你有帮助,请点赞、收藏、关注我!
代码看不懂?评论区提问,我会详细解答!
关键词:#ArkTS #TypeScript #装饰器 #异步编程 #类型系统 #HarmonyOS开发
本文 GitHub:Today_is_blank
系列目录:README
更多推荐


所有评论(0)