目录
- 开发环境准备
- ArkTS 语言
- ArkUI-基础
- ArkUI-高级
1. 开发环境准备
在开始写代码前,你需要安装IDE。
1.开发环境搭建(超详细版)
1.1 系统要求与准备工作
软件预依赖
- Node.js:版本 14.x ~ 18.x
- OpenJDK:DevEco Studio 内置,无需单独配置
- Python:3.8+(可选,某些工具链需要)
1.2 下载 DevEco Studio
步骤一:访问下载地址
打开浏览器,进入 地址:
步骤二:选择版本
你会看到两个版本:
- DevEco Studio(正式版):面向商业项目,稳定。
- DevEco Studio(Beta版):包含最新特性,学习用推荐。
初学者建议选择最新正式版。
步骤三:下载对应系统版本
点击下载按钮,根据你的操作系统选择:
1.3 安装 DevEco Studio(Windows 为例)
步骤一:运行安装程序
双击下载的 deveco-studio-x.x.x.xxx.exe,如果弹出用户账户控制,点击“是”。
步骤二:欢迎界面
点击 Next 继续。
步骤三:选择安装路径
| 配置项 | 建议 |
|---|---|
| DevEco Studio 安装路径 | 保持默认,或改为其他盘符如 D:\harmony
|
| SDK 存储路径 |
强烈建议放在非系统盘,如 D:\harmony\sdk
|
| 模拟器路径 |
强烈建议放在非系统盘,如 D:\harmony\emulator
|
步骤四:选择安装选项
勾选 所有选项。
步骤五:开始安装
点击 Install,等待进度条走完(约 5-10 分钟,视电脑性能而定)。
步骤六:完成安装
勾选 Run DevEco Studio,点击 Finish,首次启动。
1.4 首次启动与配置向导
步骤一:导入设置(可选)
如果是首次安装,选择 Do not import settings,点击 OK。
步骤二:隐私协议
阅读并勾选 I agree...,点击 Agree。
1.5 创建你的第一个项目
步骤一:新建项目
在欢迎页点击 Create Project(或菜单栏 File → New → Create Project)。
步骤二:选择模板
选择 Empty Ability(空白应用模板),点击 Next。
步骤三:配置项目信息
| 配置项 | 填写内容 |
|---|---|
| Project name |
MyFirstApp(项目名称,英文,可自定义) |
| Bundle name |
com.example.myfirstapp(应用包名,格式:域名反写) |
| Save location | 选择项目保存路径,建议放在代码专用目录 |
| Compile SDK | 选择最高的 API 版本 |
| Model | entry(默认) |
| Device type | 勾选 Phone(手机) |
点击 Finish,项目就创建好了。
步骤四:认识项目目录结构
MyFirstApp/
├── AppScope/ # 应用全局配置
│ └── app.json5 # 应用包名、版本号等
├── entry/ # 主模块
│ └── src/
│ └── main/
│ ├── ets/ # ArkTS 源码(主要写代码的地方)
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets # 应用入口
│ │ └── pages/
│ │ └── Index.ets # 默认首页(我们的主战场)
│ ├── resources/ # 资源文件(图片、字符串等)
│ │ └── base/
│ │ ├── media/ # 图片资源
│ │ ├── element/ # 字符串、颜色等
│ │ └── profile/ # 配置文件
│ └── module.json5 # 模块配置(权限、设备类型等)
└── hvigor/ # 构建配置文件
1.6 配置模拟器(本地模拟器)
方式一:使用本地模拟器(推荐)
步骤一:打开设备管理器
菜单栏:工具→ 设备管理器。
步骤二:安装模拟器镜像
- 点击 Virtual Device Manager 标签页
- 点击右上角 New Emulator
- 选择设备类型:
Phone - 点击 Next
步骤三:下载系统镜像
- 选择一个 API 版本(与项目的 Compile SDK 保持一致)
- 点击下载图标 ⬇️,下载系统镜像(约 2-3GB)
- 下载完成后,点击 Next
- 给模拟器起个名字,点击 Finish
步骤四:启动模拟器
- 在设备管理器中,点击模拟器右侧的 ▶️ 启动按钮
- 首次启动需要 2-5 分钟,请耐心等待
- 看到手机桌面即表示启动成功
1.7 运行你的第一个程序
步骤一:选择目标设备
在 DevEco Studio 顶部工具栏,设备下拉列表中选择:
- 本地模拟器名称
- 或你的真机设备名称
步骤二:运行
点击设备列表右侧的 绿色三角形 ▶️ Run 按钮(或按快捷键 Shift + F10)。
步骤三:观察构建过程
底部 Build 窗口会显示编译构建进度:
> hvigor Building...
> hvigor Finished :entry:default@CompileArkTS...
> hvigor BUILD SUCCESSFUL
出现 BUILD SUCCESSFUL 表示构建成功,应用会自动安装到模拟器/真机上。
步骤四:看到成果
模拟器/真机上会显示默认的 Index.ets 页面内容:
Hello World
恭喜你,环境搭建成功!🎉
1.8 常见问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 下载 SDK 失败/速度慢 | 网络问题 | 检查网络,或配置代理;尝试更换下载镜像源 |
| 模拟器启动黑屏/卡死 | 显卡驱动问题 | 更新显卡驱动;检查 BIOS 是否开启虚拟化技术(VT-x/AMD-V) |
| 运行按钮灰色不可点 | 没有选择设备 | 确保模拟器已启动或真机已连接 |
hvigor BUILD FAILED |
代码或配置错误 | 仔细查看 Build 窗口中的错误信息,逐条排查 |
2. ArkTS 语言
ArkTS 是鸿蒙生态的主力开发语言,它在 TypeScript 的基础上进行了拓展和优化。
第二章:ArkTS 核心语法详解
2.1 变量与常量
变量声明(let)
在ArkTS中,使用let关键字声明可变的变量:
// 常量必须在声明时初始化
const PI: number = 3.14159;
const APP_NAME = "我的应用";
const MAX_SIZE = 100;
// 以下代码会报错
// PI = 3.14; // 错误:常量不可重新赋值
class User{
name:string
age:number
constructor( name:string, age:number) {
this.name = name
this.age = age
}
}
// 对象类型的常量,其属性可以修改,但引用不可变
const USER:User = new User('李四',30);
console.log('age1:',USER.age)
USER.age = 31;
console.log('age2:',USER.age)
// USER = new User(王五",25 ) // 错误:不能重新赋值常量引用
2.2 数据类型
基础类型
数字类型(number),字符串类型(string),布尔类型(boolean),空值(void)
// 数字类型(整数和浮点数统一使用 number)
let integer: number = 42;
let float: number = 3.14;
let hex: number = 0xFF; // 十六进制
let binary: number = 0b1010; // 二进制
let octal: number = 0o744; // 八进制
// 字符串类型
let singleQuotes: string = '单引号字符串';
let doubleQuotes: string = "双引号字符串";
let backticks: string = `模板字符串,支持 ${integer} 的插值`;
// 布尔类型
let isCompleted: boolean = false;
let hasPermission: boolean = true;
// 空值(void)通常用于函数无返回值
// null 和 undefined
let u: undefined = undefined;
let n: null = null;
联合类型
// 一个变量可以是多种类型之一
let value: string | number;
value = "Hello";
value = 123;
// value = true; // 错误:不能赋值为 boolean
类型断言
类型断言用于手动告诉编译器变量的实际类型,绕过自动类型推断,不会改变运行时类型,仅作用于编译阶段。
// 当你知道变量确切的类型时,可以使用类型断言
let someValue: any = "这是一个字符串";
let strLength: number = (someValue as string).length;
2.3 运算符
算术运算符
- 加法, - 减法, 乘法,/ 除法, %取模(余数),* 幂运算,++|-- 自增自减
let a = 10, b = 3;
console.log(a + b); // 13 - 加法
console.log(a - b); // 7 - 减法
console.log(a * b); // 30 - 乘法
console.log(a / b); // 3.333... - 除法
console.log(a % b); // 1 - 取模(余数)
console.log(a ** b); // 1000 - 幂运算
// 自增自减
let count1 = 5;
console.log(++count1); // 6 - 先自增再使用
console.log(count1++); // 6 - 先使用再自增
console.log(count1); // 7
赋值运算符
+=,-=,=,/=,%=,*=
let x = 10;
x += 5; // x = x + 5 = 15
x -= 3; // x = x - 3 = 12
x *= 2; // x = x * 2 = 24
x /= 4; // x = x / 4 = 6
x %= 5; // x = x % 5 = 1
x **= 2; // x = x ** 2 = 1
比较运算符
==,!=,===严格相等(值和类型),!==严格不等,>,<,>=,<=
let p = 5, q = 10;
console.log(p == q); // false - 相等
console.log(p != q); // true - 不等
console.log(p === q); // false - 严格相等(值和类型)
console.log(p !== q); // true - 严格不等
console.log(p > q); // false - 大于
console.log(p < q); // true - 小于
console.log(p >= 5); // true - 大于等于
console.log(q <= 10); // true - 小于等于
逻辑运算符
&&,||,!,?.(可选链操作符-安全访问嵌套属性)
let isLogin = true;
let isAdmin = false;
// 逻辑与 (&&) - 所有条件为真才为真
console.log(isLogin && isAdmin); // false
// 逻辑或 (||) - 任一条件为真即为真
console.log(isLogin || isAdmin); // true
// 逻辑非 (!) - 取反
console.log(!isLogin); // false
console.log(!isAdmin); // true
// 短路求值
let user = null;
let userName = user || "游客"; // 如果user为null/undefined,使用默认值
console.log(userName); // "游客"
// 可选链操作符 (?.) - 安全访问嵌套属性
let obj: any = { data: { value: 42 } };
console.log(obj?.data?.value); // 42
console.log(obj?.nonexistent?.value); // undefined(不会报错)
2.4 流程控制
条件语句
if ,switch
// if-else 语句
let score1 = 85;
if (score1 >= 90) {
console.log("优秀");
} else if (score1 >= 75) {
console.log("良好");
} else if (score1 >= 60) {
console.log("及格");
} else {
console.log("不及格");
}
// switch 语句
let day = 3;
let dayName: string;
switch (day) {
case 1:
dayName = "星期一";
break;
case 2:
dayName = "星期二";
break;
case 3:
dayName = "星期三";
break;
case 4:
dayName = "星期四";
break;
case 5:
dayName = "星期五";
break;
case 6:
dayName = "星期六";
break;
case 7:
dayName = "星期日";
break;
default:
dayName = "无效的日期";
}
console.log(dayName); // "星期三"
循环语句
for,while,do-while
// for 循环
console.log("for 循环:");
for (let i = 1; i <= 5; i++) {
console.log(`计数: ${i}`);
}
// while 循环
console.log("while 循环:");
let countDown = 5;
while (countDown > 0) {
console.log(`倒计时: ${countDown}`);
countDown--;
}
// do-while 循环(至少执行一次)
console.log("do-while 循环:");
let num = 0;
do {
console.log(`数字: ${num}`);
num++;
} while (num < 3);
// for-of 循环(遍历数组/可迭代对象)
let colors = ["红", "绿", "蓝"];
console.log("for-of 循环:");
for (let color of colors) {
console.log(color);
}
// for-in 循环(遍历对象属性)
let person = { name: "张三", age: 25, city: "上海" };
console.log("for-in 循环:");
for (let key in person) {
console.log(`${key}: ${person[key]}`);
}
跳转语句
break,continue
// break - 跳出循环
for (let i = 1; i <= 10; i++) {
if (i === 5) {
break; // i=5时跳出循环
}
console.log(i); // 输出: 1,2,3,4
}
// continue - 跳过当前迭代
for (let i = 1; i <= 5; i++) {
if (i === 3) {
continue; // 跳过3
}
console.log(i); // 输出: 1,2,4,5
}
2.5 数组与枚举
数组
// 数组声明方式
let numbers1: number[] = [1, 2, 3, 4, 5];
let numbers2: Array<number> = [6, 7, 8, 9, 10];
let mixed: (string | number)[] = ["hello", 42, "world", 100];
// 数组操作
let fruits: string[] = ["苹果", "香蕉", "橙子"];
// 添加元素
fruits.push("葡萄"); // 末尾添加
fruits.unshift("草莓"); // 开头添加
console.log(fruits); // ["草莓", "苹果", "香蕉", "橙子", "葡萄"]
// 删除元素
let last = fruits.pop(); // 删除最后一个元素,返回 "葡萄"
let first = fruits.shift(); // 删除第一个元素,返回 "草莓"
console.log(fruits); // ["苹果", "香蕉", "橙子"]
// 访问和修改
console.log(fruits[0]); // "苹果"
fruits[1] = "西瓜";
console.log(fruits); // ["苹果", "西瓜", "橙子"]
// 数组常用方法
let nums:Array<number> = [1, 2, 3, 4, 5];
// map - 转换每个元素
let doubled:Array<number> = nums.map(n => n * 2);
console.log('结果:',doubled); // [2, 4, 6, 8, 10]
// filter - 过滤元素
let evens:Array<number> = nums.filter(n => n % 2 === 0);
console.log('结果:',evens); // [2, 4]
// reduce - 累积计算
let sum:number = nums.reduce((acc, curr) => acc + curr, 0);
console.log('结果:',sum); // 15
// forEach - 遍历
nums.forEach(n => console.log(`数字: ${n}`));
// find - 查找元素
let found:number = nums.find(n => n > 3);
console.log('结果:',found); // 4
// some/every - 条件判断
console.log('结果:',nums.some(n => n > 4)); // true (是否有大于4的)
console.log('结果:',nums.every(n => n > 0)); // true (是否都大于0)
// 数组展开运算符
let arr1:Array<number> = [1, 2, 3];
let arr2:Array<number> = [4, 5, 6];
let combined:Array<number> = [...arr1, ...arr2];
console.log('结果:',combined); // [1, 2, 3, 4, 5, 6]
枚举(Enum)
枚举 = 给一组固定不变的常量起有意义的名字
让代码不用写魔法数字、魔法字符串,一眼看懂含义,还能避免写错值。
// 数字枚举
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
console.log('UP:',Direction.Up); // 0
// 自定义初始值
enum Status {
Pending = 1,
Approved = 2,
Rejected = 3
}
// 字符串枚举
enum Color {
Red = "红色",
Green = "绿色",
Blue = "蓝色"
}
console.log(Color.Red); // "红色"
// 枚举的使用场景
enum UserRole {
Guest = 0,
User = 1,
Admin = 2
}
function checkPermission(role: UserRole):void {
if (role === UserRole.Admin) {
console.log("拥有所有权限");
} else if (role === UserRole.User) {
console.log("拥有普通权限");
} else {
console.log("只有浏览权限");
}
}
checkPermission(2); // "拥有所有权限"
// 常量枚举(编译后会被内联,性能更好)
const enum Weekday {
Monday = "周一",
Tuesday = "周二",
Wednesday = "周三"
}
console.log(Weekday.Monday); // 编译后直接替换为 "周一"
2.6 函数
函数 = 可以重复使用的代码块
把一段逻辑封装起来,需要时直接调用,不用重复写代码。
函数声明与表达式
函数声明方式
// 函数声明
//a,b是函数参数
//:number是函数返回值类型
function add(a: number, b: number): number {
return a + b;
}
// 箭头函数
let multiply = (a: number, b: number): number => a * b;
console.log('结果1:',add(5, 3));
console.log('结果2:',multiply(6, 7));
参数特性
1.必选参数(默认规则)
必须传参,数量、类型严格匹配,ArkTS 强类型约束,不允许隐式缺省。
2.可选参数 ?
参数名后加 ?,表示该参数可传、可不传。
3.默认参数
定义时直接赋值,不传参则使用默认值。
4.剩余参数(不定参数)...
用 ...参数名: 类型[] 接收多个不确定数量的同类型参数,把零散参数收集为数组。
规则:剩余参数只能写在参数列表最后一位
// 可选参数(使用 ?)
function greet1(name: string, title?: string): string {
if (title) {
return `你好,${title} ${name}`;
}
return `你好,${name}`;
}
console.log(greet1("张三"));
console.log(greet1("张三", "经理"));
// 默认参数
function greet2(name: string, greeting: string = "你好"): string {
return `${greeting},${name}`;
}
console.log(greet2("李四"));
console.log(greet2("王五", "欢迎"));
// 剩余参数(不定数量参数)
function sumAll(...numbers: number[]): number {
return numbers.reduce((total:number, num:number):number => total + num, 0);
}
console.log('结果:'+sumAll(1,2,3));
返回值
// 无返回值(void)
function logMessage(message: string): void {
console.log(`[LOG] ${message}`);
}
// 函数作为返回值
function multiplyBy(factor: number): (num: number) => number {
return function(num: number): number {
return num * factor;
};
}
let double = multiplyBy(2);
let triple = multiplyBy(3);
console.log('结果:'+double(5)); // 10
console.log('结果:'+triple(5)); // 15
// 递归函数
function factorial(n: number): number {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log('结果:'+factorial(5)); // 120
函数重载
函数重载 = 同一个函数名,支持不同的参数类型 / 参数个数,调用时自动匹配。
让一个函数名能处理多种参数情况,代码更统一、更优雅。
// 函数重载声明
function process(value: string): string;
function process(value: number): number;
function process(value: boolean): boolean;
// 实现
function process(value: string | number | boolean): string | number | boolean {
if (typeof value === "string") {
return value.toUpperCase();
} else if (typeof value === "number") {
return value * 2;
} else {
return !value;
}
}
console.log(process("hello")); // "HELLO"
console.log(process(10)); // 20
console.log(process(true)); // false
2.7 面向对象
类的基本定义
类 = 封装属性 + 方法的模板
用来创建对象,是面向对象(OOP)的核心。
构造函数(constructor)
创建对象时自动执行,用来初始化属性。
// 简单的类
class Animal {
// 属性
name: string;
age: number;
// 构造函数
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 方法 类中的定义的函数 不需要function关键字
speak(): void {
console.log(`${this.name} 发出了叫声`);
}
getInfo(): string {
return `${this.name},${this.age}岁`;
}
}
// 创建实例
let dog = new Animal("旺财", 3);
dog.speak();
console.log(dog.getInfo());
访问修饰符
控制属性 / 方法能否被外部访问,ArkTS 强制规范。
| 修饰符 | 作用 |
|---|---|
| public | 公开(默认),任何地方可访问 |
| private | 私有,只能在当前类内部访问 |
| protected | 受保护,当前类 + 子类可访问 |
class Person {
public name: string; // 公有(默认),任何地方都可访问
private age: number; // 私有,只能在本类内部访问
protected gender: string; // 受保护,本类和子类可访问
readonly id: number; // 只读,只能在初始化时赋值
constructor(name: string, age: number, gender: string, id: number) {
this.name = name;
this.age = age;
this.gender = gender;
this.id = id;
}
// 公共方法访问私有属性
public getAge(): number {
return this.age;
}
// 修改私有属性
public setAge(age: number): void {
if (age > 0 && age < 150) {
this.age = age;
}
}
private getSecret(): string {
return "这是私有方法";
}
protected getProtectedInfo(): string {
return `性别: ${this.gender}`;
}
}
let person1 = new Person("张三", 25, "男", 1001);
console.log(person1.name);
// console.log(person1.age);
console.log(person1.getAge());
// person1.id = 1002;
继承
1.子类继承父类,复用代码,扩展功能。
使用 extends 关键字
2.子类用 super() 调用父类构造函数
3.子类可以重写父类方法
class Student extends Person {
studentId: string;
grade: number;
constructor(name: string, age: number, gender: string, id: number, studentId: string, grade: number) {
super(name, age, gender, id); // 调用父类构造函数
this.studentId = studentId;
this.grade = grade;
}
// 重写父类方法
getInfo(): string {
// 可以访问 protected 属性
return `${this.name},${this.getAge()}岁,${this.gender},学号: ${this.studentId}`;
}
study(): void {
console.log(`${this.name} 正在学习`);
}
}
let student = new Student("李华", 18, "男", 1002, "2024001", 85);
console.log(student.getInfo());
student.study();
静态成员
属于类,不属于对象,不用 new 就能调用。
class MathUtils {
static PI: number = 3.14159;
static add(a: number, b: number): number {
return a + b;
}
static multiply(a: number, b: number): number {
return a * b;
}
// 静态方法不能访问实例成员
}
// 直接通过类名调用,无需创建实例
console.log('π:'+MathUtils.PI); // 3.14159
console.log('和:'+MathUtils.add(10, 20)); // 30
console.log('积:'+MathUtils.multiply(5, 6)); // 30
Getter 和 Setter
安全地读取 / 修改私有属性。
class Temperature {
private _celsius: number = 0;
get celsius(): number {
return this._celsius;
}
set celsius(value: number) {
if (value < -273.15) {
throw new Error("温度不能低于绝对零度");
}
this._celsius = value;
}
get fahrenheit(): number {
return this._celsius * 9/5 + 32;
}
set fahrenheit(value: number) {
this.celsius = (value - 32) * 5/9;
}
}
let temp = new Temperature();
temp.celsius = 25;
console.log('摄氏温度:'+temp.celsius); // 25
console.log('华氏温度:'+temp.fahrenheit); // 77
temp.fahrenheit = 100;
console.log('华氏温度:'+temp.fahrenheit); // 100
console.log('摄氏温度:'+temp.celsius); // 37.777...
2.8 接口(Interface)
接口定义规则,类必须遵守规则实现。
基础接口
// 定义接口
interface ISpeak {
speak(): void;
}
// 类实现接口
class Student implements ISpeak {
name:string;
constructor(name:string) {
this.name = name
}
speak(): void {
console.log(`学生${this.name}正在说话`);
}
}
注意ArkTS接口规则包括属性和方法
实现多个接口
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
interface AlarmInterface {
alarm(): void;
}
class SmartClock implements ClockInterface, AlarmInterface {
currentTime: Date;
constructor() {
this.currentTime = new Date();
}
setTime(d: Date): void {
this.currentTime = d;
}
alarm(): void {
console.log("闹钟响了!");
}
}
2.9 泛型
一句话讲清:泛型是什么?
泛型 = 让函数 “不提前写死类型”,调用时再指定类型。
作用:一套代码,支持所有类型,同时保留类型安全。
泛型函数
你想写一个函数:传入什么类型,就返回什么类型。
不用泛型,只能这样写:
// 坏写法:只能处理 number
function identity(arg: number): number {
return arg;
}
// 更坏写法:用 any,丢失类型检查(不安全)
function identity(arg: any): any {
return arg;
}
使用泛型解决
// 基本泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let output1 = identity<string>("hello");
let output2 = identity<number>(100);
// 泛型约束
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log('length:'+arg.length);
return arg;
}
logLength("hello");
logLength([1, 2, 3]);
// logLength(123); // 错误:数字没有 length 属性
泛型接口
泛型接口 = 把 “泛型” 用在接口上,让接口可以适配多种数据类型,保持类型安全。
// 定义泛型接口
interface Result<T> {
code: number;
data: T; // data 类型不固定,用 T 表示
msg: string;
}
// 使用:data 是 string 类型
let res1: Result<string> = {
code: 200,
data: "成功",
msg: "ok"
};
// 使用:data 是 number 类型
let res2: Result<number> = {
code: 200,
data: 100,
msg: "ok"
};
泛型类
泛型类就是给类增加泛型参数,让类的属性、方法可以适配多种类型,兼顾复用性与类型安全,鸿蒙中常用来封装工具类、数据容器、通用缓存等。
// 定义泛型类
class Container<T> {
// 属性使用泛型 T
private value: T;
constructor(val: T) {
this.value = val;
}
// 方法也可使用泛型 T
getValue(): T {
return this.value;
}
setValue(val: T): void {
this.value = val;
}
}
// 使用类时,指定具体类型
// 实例1:T 为 string
let strBox = new Container<string>("ArkTS");
console.log(strBox.getValue()); // ArkTS
// 实例2:T 为 number
let numBox = new Container<number>(666);
console.log(numBox.getValue()); // 666
2.10 常用内置对象
Date 日期对象
// 创建日期对象
let currentDate = new Date();
console.log('现在:'+currentDate);
let specificDate = new Date(2024, 0, 15, 10, 30, 0); // 2024年1月15日 10:30:00
let dateFromString = new Date("2024-12-25");
let dateFromTimestamp = new Date(1703520000000);
console.log('日期:',specificDate,dateFromString,dateFromTimestamp);
// 获取日期信息
let date = new Date();
console.log('年份',date.getFullYear()); // 年份
console.log('月份',date.getMonth()); // 月份 (0-11)
console.log('日期',date.getDate()); // 日期 (1-31)
console.log('星期几',date.getDay()); // 星期几 (0-6, 0为周日)
console.log('小时',date.getHours()); // 小时 (0-23)
console.log('分钟',date.getMinutes()); // 分钟 (0-59)
console.log('秒',date.getSeconds()); // 秒 (0-59)
console.log('毫秒',date.getMilliseconds()); // 毫秒
console.log('时间戳',date.getTime()); // 时间戳
//日期格式化
console.log( date.toString() )
console.log( date.toDateString() )
console.log( date.toTimeString() )
console.log( date.toLocaleString() )
console.log( date.toLocaleDateString() )
console.log( date.toLocaleTimeString() )
Math 数学对象
// 常用常量
console.log('π:',Math.PI); // 3.141592653589793
console.log('E:',Math.E); // 2.718281828459045
// 基本运算
console.log('结果:',Math.abs(-5)); // 5 - 绝对值
console.log('结果:',Math.ceil(4.2)); // 5 - 向上取整
console.log('结果:',Math.floor(4.9)); // 4 - 向下取整
console.log('结果:',Math.round(4.5)); // 5 - 四舍五入
console.log('结果:',Math.max(1, 5, 3)); // 5 - 最大值
console.log('结果:',Math.min(1, 5, 3)); // 1 - 最小值
console.log('结果:',Math.pow(2, 3)); // 8 - 幂运算
console.log('结果:',Math.sqrt(16)); // 4 - 平方根
console.log('结果:',Math.random()); // 0-1之间的随机数
// 生成指定范围的随机数
function randomRange(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(' 1-100之间的随机整数',randomRange(1, 100)); // 1-100之间的随机整数
Map 和 Set
// Map - 键值对集合
let userMap = new Map<string, string | number>();
userMap.set("name", "张三");
userMap.set("age", 25);
userMap.set("city", "北京");
// 获取值
console.log('结果',userMap.get("name")); // "张三"
//包含值
console.log('包含吗',userMap.has("age")); // true
// 遍历Map
userMap.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 删除和清空
userMap.delete("city");
// userMap.clear(); // 清空所有
console.log('容量:',userMap.size); // 2
// Set - 唯一值集合
let numberSet = new Set<number>();
numberSet.add(1);
numberSet.add(2);
numberSet.add(3);
numberSet.add(2); // 重复添加无效
console.log('包含2吗',numberSet.has(2)); // true
console.log('容量:',numberSet.size); // 3
// 遍历Set
numberSet.forEach(value => {
console.log('当前值:',value);
});
JSON对象
只有两个核心静态方法:
JSON.stringify() → 对象 / 值 → JSON 字符串
JSON.parse() → JSON 字符串 → 对象 / 值
let obj = { name: "张三", age: 20, isOk: true, addr: null };
let str = JSON.stringify(obj);
console.log(str);
let str = '{"name":"张三","age":20}';
let obj = JSON.parse(str);
console.log(obj.name);
3. ArkUI-基础
3.1.鸿蒙 ArkUI 入门核心章节,覆盖组件核心概念、系统常用布局/基础组件、自定义组件、多态样式、双向数据绑定。
3.1.1. 什么是 ArkUI 组件
ArkUI 是鸿蒙应用的前端UI框架,组件是 ArkUI 页面的最小组成单元。我们看到的按钮、文字、图片、布局容器,全部都是组件,页面本质就是「多个组件嵌套组合」的结果。
ArkUI 采用声明式开发范式:开发者只需要声明UI结构、数据、样式,框架自动完成页面渲染,无需手动操作DOM。
3.1.2. 组件分类
- 基础组件:最小UI单元,负责展示内容(Text、Image、Button、TextInput等)
- 布局组件:容器组件,用于控制子组件排列布局(Column、Row、Flex、Grid等)
- 自定义组件:开发者基于系统组件封装的可复用组件
3.1.3. 组件核心特征
- 结构:组件标签嵌套组合形成页面结构
- 属性:设置宽高、颜色、字体等样式
- 事件:绑定点击、滑动、输入等交互行为
- 数据:支持数据驱动视图,数据变、视图自动更新
3.1.4. 最简 ArkUI 页面示例
@Entry
@Component
struct Index {
build() {
// 布局容器 + 基础组件
Column() {
Text("Hello ArkUI")
.fontSize(30)
.fontColor(Color.Blue)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
3.2.自定义通用组件
系统内置组件无法满足业务复用需求时,可封装自定义组件,核心优势:代码复用、降低冗余、统一UI样式、便于维护。
3.2.1. 自定义组件核心规则
- 必须使用 @Component 装饰器声明
- 必须实现 build() 方法,返回UI结构
- 无 @Entry 修饰:纯自定义组件,仅供其他页面引用
- 支持自定义属性、事件,实现父子组件通信
3.2.2. 基础自定义组件
// 自定义按钮组件
@Component
export struct CustomBtn {
build() {
Button("自定义按钮")
.width(200)
.height(40)
.backgroundColor('#007DFF')
.fontColor(Color.White)
}
}
// 页面入口
//导入组件
import {CustomBtn} from '../components/CustomBtn'
@Entry
@Component
struct CustomComponentDemo {
build() {
Column({ space: 20 }) {
// 引用自定义组件
CustomBtn()
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
3.2.3. 可传参自定义组件(@Prop)
通过 @Prop 接收父组件传值,实现组件动态适配,是业务开发高频用法。
@Component
export struct CustomText {
// 接收父组件传入参数
@Prop msg: string
@Prop fontSize: number
build() {
Text(this.msg)
.fontSize(this.fontSize)
.fontColor(Color.Black)
}
}
页面使用
import {CustomText} from '../components/CustomText'
build() {
Column({ space: 15 }) {
// 给自定义组件传参
CustomText({
msg:'test',
fontSize:40
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
3.3.ArkUI 常用核心组件详解
3.3.1 布局容器组件
1.Column(垂直布局)
垂直排列子组件,是最常用的基础布局,子组件从上到下依次排布。
核心属性:space(子组件间距)、justifyContent(主轴对齐)、alignItems(交叉轴对齐)
build() {
// 垂直布局,子组件间距20
Column({ space: 20 }) {
Text("第一行文字").fontSize(20)
Text("第二行文字").fontSize(20)
Button("垂直布局按钮").width(150)
}
.width('100%')
.height(300)
.backgroundColor('#F5F5F5')
// 主轴居中
.justifyContent(FlexAlign.Center)
// 交叉轴居中
.alignItems(HorizontalAlign.Center)
}
- Row(水平布局)
水平排列子组件,子组件从左到右依次排布,适合横向排列按钮、标签、输入框等。
build() {
Row({ space: 15 }) {
Button("按钮1").width(100)
Button("按钮2").width(100)
Button("按钮3").width(100)
}
.width('100%')
.height(200)
.backgroundColor('#F5F5F5')
.justifyContent(FlexAlign.Center)
}
- Flex(弹性布局)
弹性布局,兼容水平/垂直排列,支持子组件自适应拉伸、均分空间,适配性比Row/Column更强。
build() {
Flex({ wrap: FlexWrap.Wrap,justifyContent:FlexAlign.SpaceAround }) {
ForEach([1,2,3,4,5], (item: number) => {
Text(`标签${item}`)
.width(100)
.height(40)
.backgroundColor('#E8F4FF')
.textAlign(TextAlign.Center)
})
}
.width('100%')
.padding(10)
.height(200)
}
- Grid(网格布局)
网格布局,用于多行多列规整排列内容,适合相册、商品列表、功能菜单等场景。
build() {
Grid() {
ForEach([1,2,3,4,5,6], (item: number) => {
GridItem(){
Text(`网格${item}`)
.width('100%')
.height(80)
.backgroundColor('#F0F7FF')
.textAlign(TextAlign.Center)
.fontSize(18)
}
})
}
// 每行3列,均分宽度
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(10)
.columnsGap(10)
.padding(10)
}
- Stack(堆叠布局)
层叠布局,子组件层层堆叠,后写的组件覆盖先写的,适合实现悬浮按钮、文字叠加、蒙版等效果。
build() {
Stack() {
// 底层背景
Rect().width(200).height(200).fill('#87CEEB')
// 上层文字
Text("堆叠布局")
.fontSize(22)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
}
.alignContent(Alignment.Center)
}
- Scroll(滚动容器)
单一滚动容器,当子组件内容超出屏幕高度/宽度时,支持滑动查看,适合长文本、长页面。
contents:string[] = ['世界杯','欧洲杯','亚洲杯','中超','英超']
build(){
Scroll() {
Column({ space: 10 }) {
ForEach(this.contents, (item:String,index:number)=>{
Text(`滚动内容-第${index+1}行:${item}`).fontSize(18)
})
}
.padding(10)
}
.width('100%')
.height(100)
}
- List(高性能列表)
鸿蒙官方高性能列表组件,按需渲染、复用Item,适合大量数据列表(优于Scroll),是开发列表的首选组件。
private listData: string[] = ["列表条目1","列表条目2","列表条目3","列表条目4","列表条目5"]
build() {
List() {
ForEach(this.listData, (item: string, index: number) => {
ListItem() {
Text(item)
.width('100%')
.height(60)
.fontSize(18)
.padding({left:15})
}
})
}
.width('100%')
.height(200)
.divider({strokeWidth:1, color:'#EEEEEE'})//分割线
}
3.3.2. 基础内容组件
- Text(文本组件)
用于展示文字内容,支持字体、颜色、大小、行高、超长省略、换行等全部文本样式。
Column({ space: 15 }) {
// 基础文本
Text("基础文本")
.fontSize(24)
.fontColor(Color.Black)
// 加粗、行高、颜色
Text("自定义样式文本")
.fontSize(22)
.fontColor('#007DFF')
.fontWeight(FontWeight.Bold)
.lineHeight(30)
// 超长文本省略
Text("超长文本测试超长文本测试超长文本测试")
.width(200)
.textOverflow({overflow:TextOverflow.Ellipsis})
.maxLines(1)
}
.padding(20)
- Image(图片组件)
展示本地/网络图片,支持缩放、圆角、裁剪、占位图等,是页面配图核心组件。
Column({ space: 20 }) {
// 网络图片
//Image('https://baikebcs.bdimg.com/baike-react/common/logo-baike.svg')
// 加载 resources/base/media/ 目录下的图片
Image($r('app.media.startIcon'))
.width(50)
.height(50)
// 等比例填充
.objectFit(ImageFit.Cover)
// 圆角
.borderRadius(10)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
- TextInput(输入框组件)
接收用户文字输入,支持单行/多行、密码模式、占位符、输入监听,用于登录、搜索、表单场景。
Column({ space: 20 }) {
// 普通输入框
TextInput({placeholder:"请输入用户名"})
.width(300)
.height(45)
.fontSize(18)
.border({width:1, color:'#EEEEEE'})
.borderRadius(8)
// 密码输入框
TextInput({placeholder:"请输入密码"})
.width(300)
.height(45)
.type(InputType.Password)
.border({width:1, color:'#EEEEEE'})
.borderRadius(8)
}
.width('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
- Button(按钮组件)
交互按钮,支持填充、描边、文本按钮,绑定点击事件,是核心交互组件。
Column({ space: 15 }) {
// 填充按钮
Button("确认提交")
.width(200)
.height(44)
.backgroundColor('#007DFF')
.onClick(()=>{
console.log("点击确认按钮")
})
// 描边按钮
Button("取消")
.width(200)
.height(44)
.backgroundColor(Color.Transparent)
.border({width:1, color:'#007DFF'})
.fontColor('#007DFF')
}
.width('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
- Slider(滑块组件)
滑动选择数值组件,用于音量调节、亮度调节、进度选择等场景,支持数值监听。
@State value: number = 50
build() {
Column({ space: 20 }) {
Text(`当前数值:${this.value}`).fontSize(20)
Slider({
value: this.value,
min: 0,
max: 100,
step: 1
})
.width(300)
.onChange((val: number) => {
this.value = val
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
3.4. 组件多态样式
多态样式:组件可根据不同状态(默认、按压、禁用、聚焦)自动切换样式,无需手动判断状态,简化交互样式开发。
核心API:.stateStyles()
支持状态:normal(默认)、pressed(按压)、disabled(禁用)、focused(聚焦)
3.4.1. 多态样式完整示例
@State isDisable: boolean = false
// 使用装饰器 @Styles ,
@Styles//默认样式
normalStyle() {
.backgroundColor('#005FCB')
}
@Styles//挤压样式
pressedStyle() {
.backgroundColor('#007DFF')
}
@Styles//禁用样式
disabledStyle(){
.backgroundColor('#CCCCCC')
}
build() {
Column({ space: 30 }) {
// 多态按钮
Button("多态样式按钮")
.width(220)
.height(48)
.stateStyles({
normal: this.normalStyle,
pressed: this.pressedStyle,
disabled: this.disabledStyle
})
.enabled(!this.isDisable)
// 切换禁用状态按钮
Button("切换禁用状态")
.width(220)
.onClick(()=>{
this.isDisable = !this.isDisable
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
3.5.双向数据绑定
3.5.1. 核心概念
单向绑定:数据变→视图变,视图变→数据不变(普通 @State 绑定)
双向绑定:数据和视图互相联动,视图修改自动同步数据,数据修改自动更新视图
ArkUI 双向绑定核心语法:$$
3.5.2. 基础双向绑定(TextInput)
输入框输入内容,自动同步变量;修改变量,输入框内容自动更新。
// 定义响应式数据
@State inputText: string = ""
build() {
Column({ space: 20 }) {
// 双向数据绑定 $$
TextInput({text: $$this.inputText, placeholder:"请输入内容"})
.width(300)
.height(45)
.border({width:1, color:'#EEE'})
.borderRadius(8)
// 实时展示绑定数据
Text(`实时输入内容:${this.inputText}`).fontSize(20)
// 测试修改数据,反向更新视图
Button("清空内容")
.width(150)
.onClick(()=>{
this.inputText = ""
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
3.5.3. 滑块双向绑定实战
//定义响应式数据
@State num: number = 30
build() {
Column({ space: 20 }) {
Text(`当前数值:${this.num}`).fontSize(22)
// 滑块双向绑定
Slider({value: $$this.num, min:0, max:100, step:1})
.width(300)
Button("重置为50")
.onClick(()=>{
this.num = 50
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
3.5.4. 双向绑定核心总结
- 语法:$$变量名 实现双向绑定
- 依赖:变量必须被@State 装饰为响应式数据
- 场景:输入框、滑块、开关等可交互修改视图的组件
- 优势:无需手动监听事件赋值,代码极简,数据视图自动同步
六、本章总结
- ArkUI 基于声明式开发,组件是页面核心单元,分为布局、基础、自定义组件
- 11大常用组件覆盖日常90%页面开发,List、Flex、Scroll为高频核心布局
- 自定义组件实现代码复用,支持父子传参,是工程化开发基础
- 多态样式实现组件不同状态自动切换样式,提升交互体验
- 双向数据绑定 $$ 实现视图与数据双向联动,简化交互逻辑
4. ArkUI-高级
本章将深入探讨 ArkUI 的高级特性,包括渲染控制语句、组件样式复用(@Styles、@Extend)以及组件结构复用(@Builder、@BuilderParam)。掌握这些特性将帮助你编写更优雅、可维护的鸿蒙应用代码。
4.1. 渲染控制语句
ArkUI 提供了多种渲染控制语句,用于根据条件或循环动态构建 UI。
4.1.1. 条件渲染:if/else
根据条件决定是否渲染某个组件或分支。
@State isLoggedIn: boolean = false
@State userRole: string = 'guest' // guest, user, admin
build() {
Column({ space: 20 }) {
// 基础条件渲染
if (this.isLoggedIn) {
Text('欢迎回来!')
.fontSize(18)
.fontColor('#4ECDC4')
} else {
Text('请先登录')
.fontSize(18)
.fontColor('#FF6B6B')
}
// 多分支条件渲染
if (this.userRole === 'admin') {
Row({ space: 10 }) {
Button('管理用户')
Button('系统设置')
Button('数据统计')
}
} else if (this.userRole === 'user') {
Row({ space: 10 }) {
Button('个人资料')
Button('我的订单')
}
} else {
Text('请注册成为会员')
.fontColor('#999')
}
// 控制按钮
Button(this.isLoggedIn ? '退出登录' : '登录')
.onClick(() => {
this.isLoggedIn = !this.isLoggedIn
})
Button('切换角色')
.onClick(() => {
const roles = ['guest', 'user', 'admin']
const currentIndex = roles.indexOf(this.userRole)
this.userRole = roles[(currentIndex + 1) % roles.length]
})
}
.width('100%')
.padding(20)
}
4.1.2 循环渲染:ForEach
基于数组数据重复渲染相同结构的组件。
基本语法结构
ForEach(
数据源数组,
(item) => { // 每一项渲染
// 你的 UI 组件
},
(item) => 唯一标识 // 必须写!
)
商品项组件
@Component
export struct ProductItem {
@Prop product: Product
build() {
Row({ space: 15 }) {
// 商品图标
Column() {
Image($r('app.media.startIcon'))
.width(50)
.height(50)
.borderRadius(8)
}
// 商品信息
Column() {
Text(this.product.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(`¥${this.product.price}`)
.fontSize(14)
.fontColor('#FF6B6B')
if (!this.product.inStock) {
Text('缺货')
.fontSize(12)
.fontColor('#999')
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(10)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
}
export interface Product{
id: number,
name: string,
price: number,
inStock: boolean
};
商品列表
import {ProductItem} from '../components/ProductItem'
import {Product} from '../components/ProductItem'
@Entry
@Component
struct Index {
@State products: Array<Product> = [
{
id: 1,
name: '鸿蒙手机',
price: 3999,
inStock: true
},
{
id: 2,
name: '智能手表',
price: 1299,
inStock: true
},
{
id: 3,
name: '无线耳机',
price: 899,
inStock: false
},
{
id: 4,
name: '平板电脑',
price: 2999,
inStock: true
}
]
build() {
Column({ space: 20 }) {
// 标题栏
Row() {
Text('商品列表')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Blank()
}
.width('100%')
// 使用 ForEach 渲染商品列表
ForEach(
this.products,
(product:Product) => {
ProductItem({
product: product
})
} ,(product:Product) => product.id.toString()
)
} .width('100%')
.height('100%')
.padding(20)
}
}
4.2 组件样式复用
4.2.1 @Styles 装饰器:通用样式复用
定义:@Styles 后面跟方法名
使用:组件后面直接写 .样式名()
@Styles
commonStyle() {
.backgroundColor(Color.White)
.borderRadius(12)
.padding(15)
.shadow({ radius: 6, color: '#E0E0E0', offsetY: 2 })
}
build() {
Scroll() {
Column({ space: 16 }) {
// 应用样式
Column() {
Text('卡片标题')
Text('这是卡片内容,应用了统一的卡片样式和文本样式')
.fontSize(14)
.fontColor('#666')
.margin({ top: 8 })
}
.commonStyle()
// 带参数的样式使用
Row({ space: 12 }) {
Button('主要按钮')
.fontColor(Color.Red)
Button('危险按钮')
.fontColor(Color.Brown)
}
.width('100%')
.justifyContent(FlexAlign.Center)
.commonStyle()
}
.padding(20)
}
}
4.2.2 @Extend 装饰器
@Extend:给某一种特定组件(Button/Text/Column)扩展专属样式 + 事件
一句话:@Extend = 专属定制皮肤 + 点击事件
@Extend定义 到组件外面、文件最上方(必须全局)
核心语法(必须记住)
// 给 Text 扩展样式
@Extend(Text)
function 样式名() {
.fontSize(20)
.fontColor(Color.Red)
.onClick(() => {
console.log('Text 被点击了')
})
}
@Extend(Text)
function title() {
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333')
.margin({ bottom: 4 })
}
Text('测试').title()
4.2.3 @Styles vs @Extend 对比
核心区别一句话总结
@Styles:通用样式函数,可以给任意组件使用,只能定义基础样式属性。
@Extend:专属组件扩展,只能给指定单个组件使用,能定义专属属性 + 事件 + 样式。
4.3 组件结构复用
4.3.1 @Builder 装饰器:UI 结构复用
@Builder 是「轻量级可复用 UI 片段」,能封装一整段结构 + 样式 + 事件,还能传参,比 @Extend/@Styles 强很多。
- 把重复的一段 UI(不管几个组件)抽成函数
- 可传参、可包含布局 / 组件 / 样式 / 事件
- 分两种:全局 Builder、组件内私有 Builder
1.全局Builder
定义全局Builder
@Builder
export function GlobalCard(title: string, content: string) {
Column({ space: 8 }) {
// 标题
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#000')
// 内容
Text(content)
.fontSize(14)
.fontColor('#666')
// 按钮
Button('查看详情')
.width('100%')
.height(40)
.backgroundColor('#317aff')
.onClick(() => {
console.log('全局卡片点击:', title)
})
}
.width('100%')
.padding(16)
.backgroundColor('#f7f8fa')
.borderRadius(12)
.shadow({ radius: 4, color: '#00000010' })
}
使用全局Builder
import {GlobalCard} from '../common/builder/GlobalCard'
GlobalCard('my','好好学习')
2.组件内私有 Builder
// 私有Builder
@Builder
private MyCard(title: string, msg: string) {
Column() {
Text(title).fontSize(16).fontWeight(FontWeight.Bold)
Text(msg).fontSize(14).fontColor('#666')
}
.padding(10)
.backgroundColor('#f5f5f5')
.borderRadius(8)
}
build() {
Column() {
Text('Hello World')
// 调用私有Builder
this.MyCard('我是卡片', '这是卡片内容')
}
}
4.3.2 @BuilderParam:组件内容定制(插槽)
@BuilderParam 就是 ArkTS 里的 “插槽 slot”:
在自定义组件里留一个 “位置”,让父组件传入任意 UI 结构来填充这个位置。
一句话:组件壳子不变,内容由外部定制。
1.最简单案例(单插槽)
子组件(定义插槽)
// 自定义卡片组件
@Component
export struct Card {
// 默认内容
@Builder
defaultContent() {
Text('默认内容').fontColor('#999')
}
// 1. 默认插槽:没有传就用默认内容
@BuilderParam content: () => void = this.defaultContent
build() {
Column() {
Text('我是卡片标题').fontSize(18).fontWeight(FontWeight.Bold)
// 2. 插槽位置:调用 BuilderParam
this.content()
}
.padding(16)
.backgroundColor('#f5f5f5')
.borderRadius(12)
}
}
父组件(使用组件,传内容)
import {Card} from '../common/components/Card'
Column({ space: 20 }) {
// 方式1:不传内容 → 显示默认内容
Card()
// 方式2:尾随闭包 → 自定义插槽内容(最常用)
Card() {
Text('我是外部传入的内容').fontColor('#333')
Button('点我')
.margin({ top: 8 })
.onClick(() => console.log('点击'))
}
}
.padding(20)
重点:只有一个 @BuilderParam 时,才能用尾随闭包 { ... } 直接传 UI。
2.多插槽案例(头部 + 内容 + 底部)
子组件(多个 BuilderParam)
@Component
export struct Layout {
//默认内容
@Builder defHeader() { Text('默认头部') }
@Builder defMain() { Text('默认主体') }
@Builder defFooter() { Text('默认底部') }
// 多个插槽
@BuilderParam header: () => void = this.defHeader
@BuilderParam main: () => void = this.defMain
@BuilderParam footer: () => void = this.defFooter
build() {
Column() {
this.header()//header插槽位置
Divider()
this.main()//main插槽位置
Divider()
this.footer()//footer插槽位置
}
.width('100%')
.height(300)
.border({ width: 1, color: '#eee' })
}
}
父组件(给每个插槽传 Builder)
import {Layout} from '../common/components/Layout'
// 父组件定义多个 Builder 指定插槽内容
@Builder
myHeader() {
Text('自定义头部').fontSize(20).fontWeight(FontWeight.Bold)
}
@Builder
myMain() {
Text('自定义主体内容')
Image($r('app.media.startIcon'))
}
@Builder
myFooter() {
Button('提交')
}
Column({ space: 20 }) {
// 多插槽必须用参数方式传,不能尾随闭包
Layout({
header: this.myHeader,
main: this.myMain,
footer: this.myFooter
})
}
.padding(20)
4.4 综合实战:图书卡片
综合运用本章所学知识,构建一个功能完善的商品卡片组件。
1.图书组件
//图书数据结构
export interface Book {
id: string
name: string
image: Resource
tags: string[]
isHot?: boolean
isNew?: boolean
}
@Component
export struct BookCard {
@Prop book: Book;
@BuilderParam extraContent?: () => void // 额外内容插槽
@BuilderParam actionButton?: () => void // 操作按钮插槽
// 默认操作按钮
@Builder
defaultActionButton() {
Button('收藏')
.fontSize(12)
.backgroundColor('#FF6B6B')
.borderRadius(20)
.height(32)
.padding({ left: 16, right: 16 })
}
// 样式复用
@Styles
tagStyle() {
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
}
build() {
Column() {
// 图片区域
Stack({ alignContent: Alignment.TopStart }) {
Image(this.book.image)
.width('100%')
.height(180)
.objectFit(ImageFit.Fill)
// 标签徽章
if (this.book.isHot) {
Text('热门')
.tagStyle()
.backgroundColor('#FF6B6B')
.fontColor(Color.White)
.margin({ top: 8, left: 8 })
} else if (this.book.isNew) {
Text('新书')
.tagStyle()
.backgroundColor('#4ECDC4')
.fontColor(Color.White)
.margin({ top: 8, left: 8 })
}
}
// 信息
Column() {
Text(this.book.name)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 标签行
Row({ space: 6 }) {
ForEach(this.book.tags, (tag: string,index:number) => {
Text(tag)
.tagStyle()
.backgroundColor('#F0F0F0')
.fontColor('#666')
},(index:number)=> index.toString())
}
.margin({ top: 8 })
// 额外内容插槽
if (this.extraContent) {
this.extraContent()
}
}
.padding(12)
.alignItems(HorizontalAlign.Center)
Blank()
// 操作按钮
Row() {
if( this.actionButton) {
this.actionButton()
} else{
this.defaultActionButton()
}
}
.padding(12)
.borderRadius({ bottomLeft: 12, bottomRight: 12 })
.backgroundColor('#FAFAFA')
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 4, color: '#E0E0E0', offsetY: 2 })
}
}
2.图书列表页面
import { Book,BookCard } from '../components/BookCard'
@Entry
@Component
struct BookListPage {
@State books: Book[] = [
{
id: '1',
name: '万物有光',
image: $r('app.media.book1'),
tags: ['医疗', '人文', '教育'],
isHot: true
},
{
id: '2',
name: '盗墓笔记·南部档案',
image: $r('app.media.book2'),
tags: ['小说', '玄幻'],
isNew: true
}
]
@Builder
likeExtra() {
Row() {
Text('❤️')
.fontSize(10)
.fontColor('#4ECDC4')
}
.margin({ top: 8 })
}
@Builder
addWishListButton() {
Button('加入心愿清单')
.fontSize(12)
.backgroundColor('#4ECDC4')
.borderRadius(20)
.height(32)
}
build() {
Column() {
// 页面标题
Row() {
Text('图书推荐')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Blank()
Text(`共${this.books.length}本图书`)
.fontSize(14)
.fontColor('#999')
}
.width('100%')
.padding(16)
// 商品列表
Scroll() {
Column({ space: 16 }) {
ForEach(this.books, (book: Book) => {
if (book.id === '1') {
BookCard({
book: book,
extraContent: this.likeExtra,
actionButton: this.addWishListButton
})
} else if (book.id === '2') {
BookCard({
book: book,
})
}
})
}
.padding(16)
}
.scrollBar(BarState.Auto)
}
.backgroundColor('#F5F5F5')
}
}
4.5 本章小结
本章学习了 ArkUI 的高级特性:
核心知识点回顾
-
渲染语句
-
if/else:条件渲染,根据状态动态显示不同内容 -
ForEach:循环渲染,基于数组创建列表
-
-
样式复用
-
@Styles:通用样式复用,适用于所有组件 -
@Extend:为特定组件扩展样式,可以使用组件专属属性
-
-
结构复用
-
@Builder:复用 UI 结构,支持参数传递 -
@BuilderParam:组件内容定制,实现灵活的插槽机制
-
最佳实践建议
-
选择合适的复用方式:纯样式用
@Styles,组件专属样式用@Extend,UI 结构用@Builder -
合理使用插槽:
@BuilderParam让组件更灵活,但不要过度使用 - 保持组件纯净:复用组件的逻辑应该清晰单一