本篇学习 ArkTS 的核心语法,并为「古今职鉴」项目创建第一个数据模型

图:古今职鉴开源教程封面。本篇围绕「ArkTS 语言基础:TypeScript 的鸿蒙进化」展开。

学习目标

完成本篇后,你将能够:

  • ✅ 理解 ArkTS 与 TypeScript 的区别
  • ✅ 掌握变量、函数、接口、枚举的定义
  • ✅ 了解 ArkTS 的特殊限制规则
  • ✅ 为项目创建官职(Position)数据模型

预计学习时间

约 90-120 分钟

前置准备

  • 已完成第2篇,DevEco Studio 环境配置完成
  • 已创建 jiaocheng 教程项目

---

第一部分:理解 ArkTS

1.1 ArkTS 是什么?

ArkTS 是华为为鸿蒙操作系统设计的开发语言,它基于 TypeScript 进行了扩展和限制。

ArkTS 的定位:

JavaScript (动态类型,灵活但容易出错)
    ↓ 添加类型系统
TypeScript (可选静态类型,更安全)
    ↓ 限制动态特性 + 添加UI语法
ArkTS (强制静态类型,高性能)

为什么华为要创造 ArkTS?

目标 TypeScript 的问题 ArkTS 的解决方案
类型安全 any 类型绕过检查 禁止使用 any
性能优化 动态属性无法优化 禁止动态属性访问
代码可维护 对象结构不明确 强制类型声明
编译优化 运行时类型检查 编译期类型检查

1.2 ArkTS 与 TypeScript 的主要区别

特性 TypeScript ArkTS
any 类型 ✅ 允许 ❌ 禁止
动态属性访问 ✅ 允许 obj[key] ❌ 禁止
对象字面量 可无类型 必须有类型声明
索引签名 ✅ 允许 [key: string] ❌ 禁止,用 Map 替代
UI 语法 ❌ 不支持 ✅ 支持声明式 UI
装饰器 实验性支持 ✅ 原生支持 @Component 等

💡 记住:ArkTS 牺牲了一些灵活性,换来了更好的性能和类型安全。

---

第二部分:基础语法实战

步骤 1:创建练习目录

操作:在 DevEco Studio 中创建第3课目录

  1. 在项目导航器中,右键点击 products/jiaocheng/src/main/ets/
  2. 选择 NewDirectory,输入 lesson03
  3. 右键点击 lesson03 文件夹
  4. 选择 NewDirectory,输入 models

为什么这样做

  • 按课程组织代码,便于学习和查找
  • models 目录专门存放数据模型,遵循关注点分离原则

步骤 2:学习变量声明

操作:了解 ArkTS 中的变量声明方式

// ============================================
// 变量声明
// ============================================

// ----- 1. let 声明可变变量 -----
// let 声明的变量可以被重新赋值
let userName: string = '张三';  // 声明一个字符串变量
userName = '李四';              // ✅ 可以修改值

// ----- 2. const 声明常量 -----
// const 声明的变量不能被重新赋值
const APP_NAME: string = '古今职鉴';  // 应用名称,不会改变
// APP_NAME = '新名字';               // ❌ 错误!常量不能修改

// ----- 3. 基本数据类型 -----
let age: number = 25;           // 数字类型(整数和小数都用 number)
let price: number = 99.9;       // 小数也是 number
let isVip: boolean = true;      // 布尔类型:true 或 false
let description: string = `${userName},年龄${age}`;  // 模板字符串

类型注解说明

代码 含义
let userName: string 声明变量 userName,类型是 string
: string 类型注解,告诉编译器这个变量是什么类型
const APP_NAME 常量命名习惯用大写+下划线
` ${变量} ` 模板字符串,可以嵌入变量

步骤 3:学习数组操作

// ============================================
// 数组操作
// ============================================

// ----- 1. 声明数组 -----
// 方式一:类型[] 语法(推荐)
let dynastyNames: string[] = ['秦', '汉', '唐', '宋', '明', '清'];

// 方式二:Array<类型> 语法
let rankNumbers: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9];

// ----- 2. 数组常用操作 -----
// 获取长度
let count: number = dynastyNames.length;  // 结果:6

// 访问元素(索引从0开始)
let firstDynasty: string = dynastyNames[0];   // 结果:'秦'
let lastDynasty: string = dynastyNames[5];    // 结果:'清'

// 添加元素到末尾
dynastyNames.push('元');  // 数组变成:['秦', '汉', '唐', '宋', '明', '清', '元']

// 删除末尾元素
let removed: string | undefined = dynastyNames.pop();  // 删除'元'

// ----- 3. 遍历数组 -----
// 方式一:for...of 循环(推荐,简洁)
for (let dynasty of dynastyNames) {
  console.log(dynasty);  // 依次输出:秦、汉、唐...
}

// 方式二:forEach 方法(需要索引时使用)
dynastyNames.forEach((dynasty: string, index: number) => {
  console.log(`第${index + 1}个朝代:${dynasty}`);
});

⚠️ ArkTS 特殊规则:数组类型必须明确!

// ❌ 错误:ArkTS 不允许混合类型数组(无类型声明)
// let mixed = [1, 'hello', true];

// ✅ 正确:如果确实需要混合类型,使用联合类型
let mixedArray: (number | string)[] = [1, 'hello', 2, 'world'];

步骤 4:学习函数定义

// ============================================
// 函数定义
// ============================================

// ----- 1. 基本函数 -----
// function 函数名(参数: 类型): 返回类型 { ... }
function add(a: number, b: number): number {
  return a + b;
}

// 调用函数
let sum: number = add(10, 20);  // 结果:30

// ----- 2. 箭头函数(推荐) -----
// 更简洁的写法,常用于回调函数
const multiply = (a: number, b: number): number => {
  return a * b;
};

// 单行箭头函数可以省略 {} 和 return
const square = (x: number): number => x * x;

// ----- 3. 带默认参数的函数 -----
function greet(name: string, greeting: string = '你好'): string {
  return `${greeting},${name}!`;
}

let msg1: string = greet('张三');           // 结果:"你好,张三!"
let msg2: string = greet('李四', '早上好');  // 结果:"早上好,李四!"

// ----- 4. 可选参数 -----
// 参数名后加 ? 表示可选
function createUser(name: string, age?: number): string {
  if (age !== undefined) {
    return `${name},${age}岁`;
  }
  return name;
}

// ----- 5. 无返回值的函数 -----
// 返回类型用 void 表示
function logMessage(message: string): void {
  console.log(`[LOG] ${message}`);
}

函数语法速查

语法 说明 示例
function name() 普通函数 function add(a, b)
const name = () => {} 箭头函数 const add = (a, b) => a + b
param = value 默认参数 greeting = '你好'
param? 可选参数 age?: number
: void 无返回值 function log(): void

---

第三部分:接口与枚举

步骤 5:创建官职接口(Position)

操作:在 lesson03/models 目录下创建 Position.ets 文件

这是「古今职鉴」项目的核心数据模型,用于描述古代官职的信息。

// ============================================
// 文件:lesson03/models/Position.ets
// 功能:定义官职数据模型
// ============================================

/**
 * 官职信息接口
 * 
 * 为什么用 interface?
 * 1. 明确数据结构:告诉其他开发者这个对象长什么样
 * 2. 类型检查:编译器会检查对象是否符合接口定义
 * 3. 代码提示:编辑器会根据接口提供属性提示
 */
export interface Position {
  // 必填属性
  id: number;           // 唯一标识,用于区分不同官职
  name: string;         // 官职名称,如"丞相"、"太尉"
  dynasty: string;      // 所属朝代,如"秦"、"汉"
  rank: number;         // 品级,1-9品,1品最高
  category: string;     // 类别:"文官" 或 "武官"
  description: string;  // 职责描述
  
  // 可选属性(属性名后加 ?)
  modernEquivalent?: string;  // 现代对应职位,可能没有对应
  salary?: string;            // 俸禄,部分官职资料不全
}

/**
 * 创建官职对象的工厂函数
 * 
 * 为什么用工厂函数?
 * 1. 简化创建过程:不用每次都写完整的对象结构
 * 2. 参数校验:可以在函数内添加验证逻辑
 * 3. 默认值处理:可以为可选参数提供默认值
 */
export function createPosition(
  id: number,
  name: string,
  dynasty: string,
  rank: number,
  category: string,
  description: string
): Position {
  return {
    id: id,
    name: name,
    dynasty: dynasty,
    rank: rank,
    category: category,
    description: description
  };
}

代码解释

代码 含义
export interface Position 导出接口,其他文件可以导入使用
id: number 必填属性,类型是数字
modernEquivalent?: string 可选属性,创建对象时可以不填
export function createPosition 导出工厂函数

步骤 6:创建朝代枚举(Dynasty)

操作:在 lesson03/models 目录下创建 Dynasty.ets 文件

// ============================================
// 文件:lesson03/models/Dynasty.ets
// 功能:定义朝代枚举和相关工具函数
// ============================================

/**
 * 朝代枚举
 * 
 * 为什么用 enum 而不是字符串?
 * 1. 防止拼写错误:Dynasty.QIN 不会写错,'秦' 可能写成 '奏'
 * 2. 代码提示:输入 Dynasty. 会自动提示所有选项
 * 3. 重构方便:如果要改名,只需改一处
 */
export enum Dynasty {
  QIN = '秦',       // 秦朝 (公元前221年 - 公元前207年)
  HAN = '汉',       // 汉朝 (公元前202年 - 公元220年)
  WEI_JIN = '魏晋', // 魏晋南北朝 (公元220年 - 公元589年)
  TANG = '唐',      // 唐朝 (公元618年 - 公元907年)
  SONG = '宋',      // 宋朝 (公元960年 - 公元1279年)
  YUAN = '元',      // 元朝 (公元1271年 - 公元1368年)
  MING = '明',      // 明朝 (公元1368年 - 公元1644年)
  QING = '清'       // 清朝 (公元1644年 - 公元1912年)
}

/**
 * 朝代详细信息接口
 */
export interface DynastyInfo {
  id: Dynasty;        // 朝代枚举值
  name: string;       // 朝代全称,如"秦朝"
  startYear: number;  // 起始年份(负数表示公元前)
  endYear: number;    // 结束年份
  capital: string;    // 都城
  description: string; // 简介
}

/**
 * 获取朝代主题色
 * 
 * 为什么每个朝代有不同颜色?
 * - 提升用户体验,让界面更有辨识度
 * - 颜色参考了各朝代的历史文化特征
 */
export function getDynastyColor(dynasty: Dynasty): string {
  switch (dynasty) {
    case Dynasty.QIN:
      return '#1a1a1a';  // 黑色 - 秦尚黑
    case Dynasty.HAN:
      return '#c41e3a';  // 红色 - 汉尚赤
    case Dynasty.WEI_JIN:
      return '#4a7c59';  // 青绿 - 魏晋风流
    case Dynasty.TANG:
      return '#ffd700';  // 金色 - 盛唐气象
    case Dynasty.SONG:
      return '#4169e1';  // 蓝色 - 宋代青花
    case Dynasty.YUAN:
      return '#228b22';  // 绿色 - 草原民族
    case Dynasty.MING:
      return '#8b0000';  // 深红 - 明代官服
    case Dynasty.QING:
      return '#4b0082';  // 紫色 - 清代皇家
    default:
      return '#333333';  // 默认灰色
  }
}

/**
 * 获取所有朝代信息列表
 */
export function getDynastyList(): DynastyInfo[] {
  return [
    {
      id: Dynasty.QIN,
      name: '秦朝',
      startYear: -221,
      endYear: -207,
      capital: '咸阳',
      description: '中国历史上第一个统一的中央集权制国家'
    },
    {
      id: Dynasty.HAN,
      name: '汉朝',
      startYear: -202,
      endYear: 220,
      capital: '长安/洛阳',
      description: '中国历史上继秦朝之后的大一统王朝'
    },
    {
      id: Dynasty.TANG,
      name: '唐朝',
      startYear: 618,
      endYear: 907,
      capital: '长安',
      description: '中国历史上最强盛的朝代之一'
    }
  ];
}

步骤 7:创建历史人物接口

操作:在 lesson03/models 目录下创建 HistoricalFigure.ets 文件

// ============================================
// 文件:lesson03/models/HistoricalFigure.ets
// 功能:定义历史人物数据模型
// ============================================

import { Dynasty } from './Dynasty';

/**
 * 历史人物接口
 */
export interface HistoricalFigure {
  id: number;
  name: string;           // 姓名
  dynasty: Dynasty;       // 朝代(使用枚举)
  positions: string[];    // 担任过的官职
  achievements: string[]; // 主要成就
  biography: string;      // 人物简介
}

/**
 * 获取示例历史人物数据
 */
export function getSampleFigures(): HistoricalFigure[] {
  return [
    {
      id: 1,
      name: '李斯',
      dynasty: Dynasty.QIN,
      positions: ['丞相', '廷尉'],
      achievements: ['统一文字', '制定法律', '推行郡县制'],
      biography: '秦朝著名政治家,辅佐秦始皇统一六国'
    },
    {
      id: 2,
      name: '萧何',
      dynasty: Dynasty.HAN,
      positions: ['丞相'],
      achievements: ['月下追韩信', '制定汉律', '稳定后方'],
      biography: '西汉开国功臣,被誉为"汉初三杰"之一'
    },
    {
      id: 3,
      name: '魏征',
      dynasty: Dynasty.TANG,
      positions: ['尚书左丞', '侍中'],
      achievements: ['直言进谏', '贞观之治'],
      biography: '唐朝著名谏臣,以直言敢谏著称'
    }
  ];
}

---

第四部分:ArkTS 特殊规则(重要!)

ArkTS 为了性能优化,禁止了一些 TypeScript 的动态特性。这些规则必须牢记,否则编译会报错!

规则 1:❌ 禁止使用 any 类型

// ❌ 错误写法 - ArkTS 不允许 any
let data: any = { name: '张三' };

// ✅ 正确写法 - 定义具体的接口
interface UserData {
  name: string;
}
let data: UserData = { name: '张三' };

为什么禁止 any?

  • any 会绕过类型检查,失去类型安全
  • 编译器无法优化 any 类型的代码

规则 2:❌ 对象字面量必须有类型

// ❌ 错误写法 - 对象没有类型声明
let config = {
  theme: 'dark',
  fontSize: 16
};

// ✅ 正确写法 - 先定义接口,再创建对象
interface AppConfig {
  theme: string;
  fontSize: number;
}

let config: AppConfig = {
  theme: 'dark',
  fontSize: 16
};

规则 3:❌ 禁止动态属性访问

interface User {
  name: string;
  age: number;
}

let user: User = { name: '张三', age: 25 };

// ❌ 错误写法 - 动态属性访问
let key: string = 'name';
let value = user[key];  // 编译错误!

// ✅ 正确写法 - 直接访问属性
let value: string = user.name;

规则 4:❌ 禁止索引签名

// ❌ 错误写法 - 索引签名
interface Dictionary {
  [key: string]: string;  // ArkTS 不支持!
}

// ✅ 正确写法 - 使用 Map
let dictionary: Map<string, string> = new Map();
dictionary.set('hello', '你好');
dictionary.set('world', '世界');

// 获取值
let value: string | undefined = dictionary.get('hello');

规则 5:❌ @Builder 中不能使用 const/let

// ❌ 错误写法 - @Builder 中声明变量
@Builder
MyBuilder() {
  const title = '标题';  // 编译错误!
  Text(title)
}

// ✅ 正确写法 - 直接使用表达式或调用方法
@Builder
MyBuilder() {
  Text(this.getTitle())  // 调用方法获取值
}

getTitle(): string {
  return '标题';
}

---

第五部分:综合实战

步骤 8:验证代码文件

确保你已经创建了以下文件:

products/jiaocheng/src/main/ets/lesson03/
└── models/
    ├── Position.ets        # 官职接口
    ├── Dynasty.ets         # 朝代枚举
    └── HistoricalFigure.ets # 历史人物接口

步骤 9:运行构建验证

操作:在终端中运行构建命令

hvigorw assembleHap --no-daemon

预期结果:构建成功,无错误信息。

如果出现错误,请检查:

  1. 文件路径是否正确
  2. 语法是否有拼写错误
  3. 导入导出语句是否正确

---

本课小结

你学到了什么

知识点 说明
变量声明 let(可变)、const(常量)
基本类型 stringnumberboolean
数组 string[]Array<string>
函数 普通函数、箭头函数、默认参数、可选参数
接口 interface 定义对象结构
枚举 enum 定义固定常量集合
ArkTS 限制 禁止 any、必须类型声明、禁止动态访问

创建的文件

products/jiaocheng/src/main/ets/lesson03/
└── models/
    ├── Position.ets        # 官职接口和工厂函数
    ├── Dynasty.ets         # 朝代枚举和工具函数
    └── HistoricalFigure.ets # 历史人物接口

ArkTS 规则速查

规则 TypeScript ArkTS
any 类型 ✅ 允许 ❌ 禁止
无类型对象 ✅ 允许 ❌ 禁止
动态属性 obj[key] ✅ 允许 ❌ 禁止
索引签名 ✅ 允许 ❌ 禁止,用 Map
@Builder 中 const/let - ❌ 禁止

---

课后练习

练习 1:添加更多官职数据

Position.ets 中,使用 createPosition 函数创建以下官职:

  • 大司马(汉,1品,武官)
  • 太常(汉,3品,文官)
  • 郎中令(汉,4品,文官)

练习 2:扩展朝代信息

Dynasty.ets 中,为 getDynastyList 函数添加更多朝代信息:

  • 魏晋南北朝
  • 宋朝
  • 元朝
  • 明朝
  • 清朝

练习 3:创建官职分类枚举

创建一个新文件 PositionCategory.ets,定义官职分类枚举:

export enum PositionCategory {
  CIVIL = '文官',
  MILITARY = '武官',
  IMPERIAL = '内廷',
  LOCAL = '地方'
}

---

下一篇预告

[第4篇 ArkUI 核心概念:声明式 UI 入门](./04-ArkUI核心概念.md)

下一篇我们将学习:

  • 声明式 UI 的核心思想
  • @Component 和 @Entry 装饰器
  • Text、Image、Button 等基础组件
  • Column、Row、Stack 等布局容器
  • 构建「古今职鉴」首页框架

---

参考资料

---

*本教程基于「古今职鉴」真实项目编写,所有代码均经过实际验证。*


项目开源地址

https://gitcode.com/daleishen/gujinzhijian

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐