关于ArkTS和ArkUI,基础语法请看👉官方开发手册
系统学习后,聊聊几个点,面向刚学习这门语言的小白,用于巩固和回顾😋
目录
类型推断应用
函数相关
布局方式
线性布局
堆叠布局
网格布局
弹性布局
渲染相关
条件渲染
循环渲染
ForEach
Repeat
数据懒加载
装饰器
数据传递装饰器
省流版
@State
@Prop
@Link
@Provide 和 @Consume
@Observed
@ObjectLink
增强和扩展组件功能
@Extender
@Style
@Builder
自定义组件
创建自定义组件
使用 @Prop 动态传参
使用 @State 和 @Link 管理状态
使用 @BuilderParam 传递UI
router-路由操作
页面跳转和后退
页面栈
路由模式
路由传参
Stage模型
App
Module
Ability
关系和交互
生命周期流程示例
类型推断应用
类型推断是一种自动确定表达式类型的机制,能够在不明确指定类型的情况下,通过代码的上下文来推断出合适的类型。这种机制有助于减少代码中的类型声明,使代码更简洁和易于维护。
变量声明与初始化: 当你在声明变量时赋予它初始值,ArkTS会根据初始值推断变量的类型。
let num = 10; // 推断为number类型
let str = "Hello"; // 推断为string类型
let isTrue = true; // 推断为boolean类型
let numbers = [1, 2, 3]; // 推断为number[]
let user = {
name: "Alice",
age: 30
}; // 推断为{ name: string, age: number }
函数返回值: ArkTS会根据函数体内的返回值来推断函数的返回类型。
function getNumber() {
return 42; // 推断返回值类型为number
}
类型兼容性: ArkTS会在需要的时候推断出类型,以确保类型之间的兼容性。
let x = "hello";
let y = x; // 推断y的类型为string
泛型推断: 在使用泛型时,ArkTS会根据传入的参数类型来推断泛型的具体类型。
function identity<T>(arg: T): T {
return arg;
}
let output = identity(42); // 推断T为number,output的类型为number
函数相关
常规函数声明
这种方式使用 function
关键字,可以显式声明参数和返回值类型。
function add(x: number, y: number): number {
return x + y;
}
函数表达式
函数表达式可以赋值给变量,变量的类型可以显式声明。
const multiply: (x: number, y: number) => number = function(x, y) {
return x * y;
};
箭头函数
箭头函数是一种简洁的函数声明方式,常用于简化函数表达式。
const subtract = (x: number, y: number): number => {
return x - y;
};
重载函数
通过定义多个函数签名,可以实现函数重载。
function double(x: number): number;
function double(x: string): string;
function double(x: any): any {
if (typeof x === "number") {
return x * 2;
} else if (typeof x === "string") {
return x + x;
}
}
泛型函数
泛型函数允许你定义可复用的函数,并在调用时指定类型。
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString"); // T 被推断为 string
let output2 = identity<number>(100); // T 被推断为 number
参数
在ArkTS中,函数参数的处理可以非常灵活,包括可选参数、默认参数和剩余参数。这三者的综合运用可以使函数声明更加简洁和强大,适应多种调用场景。
①可选参数和默认参数
可选参数和默认参数在函数声明中都允许函数参数有灵活性:
- 可选参数使用
?
标识,使参数可以在调用时省略。 - 默认参数为参数提供默认值,当调用时未提供该参数时使用默认值。
function greet(name: string, greeting: string = "Hello", punctuation?: string): string {
if (punctuation) {
return `${greeting}, ${name}${punctuation}`;
} else {
return `${greeting}, ${name}!`;
}
}
// 示例调用
console.log(greet("Alice")); // 输出: Hello, Alice!
console.log(greet("Bob", "Hi")); // 输出: Hi, Bob!
console.log(greet("Charlie", "Hey", "?")); // 输出: Hey, Charlie?
②剩余参数
剩余参数使用 ...
语法,可以将多个参数收集到一个数组中,用于处理变长参数列表的场景。
function sum(...numbers: number[]): number {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
// 示例调用
console.log(sum(1, 2, 3)); // 输出: 6
console.log(sum(10, 20, 30, 40)); // 输出: 100
布局方式
线性布局
线性布局是最常用的布局之一,它将子元素沿水平方向或垂直方向排列。
特性:
- 可以设置子元素的排列方向:水平(row)或垂直(column)。
- 支持设置子元素之间的间距。
示例:
import { Column, Text, Button } from 'ArkUI';
Column() {
Text('Item 1');
Text('Item 2');
Button('Click Me');
}
堆叠布局
堆叠布局将子元素堆叠在一起,所有元素都位于同一个坐标位置。
特性:
- 子元素可以相互覆盖。
- 常用于需要将多个元素叠加在一起的场景,如图像叠加文本。
示例:
import { Stack, Image, Text } from 'ArkUI';
Stack() {
Image('background.png');
Text('Overlay Text');
}
网格布局
网格布局将子元素按照网格进行排列,子元素可以跨行或跨列。
特性:
- 灵活的行列定义。
- 支持子元素跨行、跨列。
示例:
import { Grid, Text } from 'ArkUI';
Grid(columns="1fr 1fr", rows="auto auto") {
Text('Item 1').gridColumn("1 / 2").gridRow("1");
Text('Item 2').gridColumn("2 / 3").gridRow("1");
Text('Item 3').gridColumn("1 / 3").gridRow("2");
}
弹性布局
弹性布局提供了一种灵活的布局方式,适用于复杂的自适应布局。
特性:
- 支持主轴和交叉轴上的元素对齐和分布。
- 子元素可以按比例缩放。
示例:
import { Flex, Text } from 'ArkUI';
Flex(direction="row", justifyContent="space-between") {
Text('Item 1');
Text('Item 2');
Text('Item 3');
}
渲染相关
条件渲染
条件渲染允许你根据某些条件来决定是否渲染某个UI组件。通常使用三元运算符或 if
语句来实现。
示例:
@Entry
@Component
struct ConditionalRenderingComponent {
@State isVisible: boolean = true;
build() {
Column() {
if (this.isVisible) {
Text('This text is visible');
} else {
Text('This text is hidden');
}
Button(this.isVisible ? 'Hide' : 'Show').onClick(() => {
this.isVisible = !this.isVisible;
});
}
}
}
循环渲染
循环渲染用于根据数组数据来渲染一组相似的UI组件。在ArkUI中,常用的循环渲染方法有 ForEach
和 Repeat
。
ForEach
ForEach
用于遍历数组并渲染每个元素。
示例:
@Entry
@Component
struct ForEachComponent {
@State items: string[] = ['Item 1', 'Item 2', 'Item 3'];
build() {
Column() {
ForEach(this.items, (item) => {
Text(item);
});
}
}
}
Repeat
Repeat
用于渲染一定次数的相同组件。
示例:
@Entry
@Component
struct RepeatComponent {
build() {
Column() {
Repeat(5, (index) => {
Text(`Item ${index + 1}`);
});
}
}
}
数据懒加载
数据懒加载用于按需加载数据,从而提高应用性能和响应速度。通常在需要加载大量数据时使用懒加载。
@Entry
@Component
struct LazyLoadComponent {
@State data: string[] = [];
@State hasMoreData: boolean = true;
onInit() {
this.loadData();
}
loadData() {
// 模拟数据加载
setTimeout(() => {
this.data = this.data.concat(['New Item 1', 'New Item 2', 'New Item 3']);
this.hasMoreData = this.data.length < 20; // 假设最多加载20条数据
}, 1000);
}
build() {
Column() {
ForEach(this.data, (item) => {
Text(item);
});
if (this.hasMoreData) {
Button('Load More').onClick(() => {
this.loadData();
});
}
}
}
}
装饰器
什么是装饰器?
类似java的注解
用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如@Entry、@Component和@State都是装饰器,@Component表示自定义组件,@Entry表示该自定义组件为入口组件,@State表示组件中的状态变量,状态变量变化会触发UI刷新。
数据传递装饰器
从数据的传递形式和同步类型层面看,装饰器也可分为:
-
只读的单向传递;
-
可变更的双向传递。
省流版
- @State:管理组件内部状态,不涉及组件间数据传递。
- @Prop:接收父组件传递的只读数据,用于单向数据流。
- @Link:实现双向数据绑定,允许子组件修改父组件的数据。
- @Provide 和 @Consume:用于跨组件层级的数据共享,适用于全局状态管理。
- @Observed:监视对象属性变化,自动触发组件重新渲染。
- @ObjectLink:传递复杂对象,允许子组件修改,适用于复杂数据管理。
@State
功能:
- 声明组件内部的状态。
- 状态数据变化时,组件自动重新渲染。
使用场景:
- 用于组件内部的可变数据管理,不涉及组件之间的数据传递。
示例:
@State count: number = 0;
区别:
- 仅限于组件内部使用,不会与其他组件共享。
@Prop
功能:
- 接收父组件传递的属性(props)。
- 这些属性是只读的,不能在子组件中修改。
使用场景:
- 用于子组件从父组件接收数据。
示例:
@Prop title: string;
区别:
- 只读属性,适用于父组件向子组件传递数据,不支持数据的双向绑定。
@Link
功能:
- 创建组件之间的数据连接,允许子组件修改父组件的数据。
- 支持数据的双向绑定。
使用场景:
- 用于需要子组件与父组件共享和修改数据的场景。
示例:
@Link count: number;
区别:
- 与
@Prop
相比,@Link
允许双向数据绑定,子组件可以更新父组件的数据。
@Provide 和 @Consume
功能:
@Provide
:在祖先组件中提供数据。@Consume
:在后代组件中消费数据。
使用场景:
- 用于跨多个组件层级的数据共享。
示例: 祖先组件:
@Provide theme: string = "dark";
后代组件:
@Consume theme: string;
区别:
- 适用于需要跨越多层组件共享数据的场景,与
@State
和@Prop
不同,主要用于更广泛的数据分发。
@Observed
功能:
- 监视对象属性的变化。
- 属性变化时,组件自动重新渲染。
使用场景:
- 用于需要自动追踪和响应数据变化的场景。
示例:
@Observed userName: string = "John";
区别:
- 与
@State
类似,但更适用于需要自动监视对象属性变化的场景。
@ObjectLink
功能:
- 允许复杂对象作为属性传递给子组件。
- 子组件可以修改这些对象。
使用场景:
- 用于复杂数据结构的共享和管理。
示例:
@ObjectLink userDetails: User;
区别:
- 与
@Link
相似,但专门用于复杂对象的数据共享和管理。
增强和扩展组件功能
在ArkTS中,@Extender
、@Style
和 @Builder
是特定的注解,用于增强和扩展组件的功能。
@Extender
功能:
@Extender
注解用于扩展现有的组件或类,允许在现有功能的基础上添加新的行为或属性。
使用场景:
- 当需要在不修改原组件或类的情况下,增加或修改功能时使用。
示例:
@Extender
class ExtendedComponent extends BaseComponent {
newMethod() {
// 新的方法或属性
}
}
区别:
@Extender
注解的主要作用是增强现有类或组件的功能,类似于装饰器模式。
@Style
功能:
@Style
注解用于为组件应用特定的样式,允许开发者在组件声明中直接定义样式。
使用场景:
- 在需要为组件指定特定样式或动态样式时使用。
示例:
@Style({
backgroundColor: 'blue',
fontSize: '14px'
})
class StyledComponent {
// 组件逻辑
}
区别:
@Style
注解使得样式定义与组件逻辑紧密结合,增强了样式管理的灵活性。
@Builder
功能:
@Builder
注解用于构建复杂对象或组件,简化了对象的创建过程,通常用于需要链式调用来设置属性的场景。
使用场景:
- 当需要简化复杂对象或组件的创建过程,使用链式调用设置多个属性时使用。
示例:
@Builder
class ComplexObject {
private prop1: string;
private prop2: number;
setProp1(value: string): this {
this.prop1 = value;
return this;
}
setProp2(value: number): this {
this.prop2 = value;
return this;
}
build(): ComplexObject {
return this;
}
}
// 使用示例
const obj = new ComplexObject().setProp1("value1").setProp2(123).build();
区别:
@Builder
注解通过链式调用的方式简化了复杂对象的构建过程,增强了代码的可读性和可维护性。
自定义组件
创建自定义组件
自定义组件通过 @Component
装饰器定义。以下是一个简单的示例:
示例:创建一个简单的 MyButton
组件。
@Component
struct MyButton {
@Prop label: string;
build() {
Button(this.label).onClick(() => {
console.log(`${this.label} clicked`);
});
}
}
在这个示例中,MyButton
组件接收一个 label
属性,并在按钮点击时输出该标签。
使用 @Prop
动态传参
@Prop
注解用于父组件向子组件传递数据。
示例:
@Entry
@Component
struct HomePage {
build() {
Column() {
MyButton({ label: 'Click Me' });
}
}
}
使用 @State
和 @Link
管理状态
示例
HomePage
组件使用@State
管理parentCount
。Counter
组件通过@Link
接收并修改count
,实现与父组件HomePage
的数据绑定和状态同步。
// HomePage.ets
@Entry
@Component
struct HomePage {
@State parentCount: number = 0;
build() {
Column() {
Text(`Parent Count: ${this.parentCount}`);
Counter({ count: this.parentCount });
}
}
}
// Counter.ets
@Component
struct Counter {
@Link count: number;
build() {
Column() {
Text(`Count: ${this.count}`);
Button('Increment').onClick(() => {
this.count += 1;
});
}
}
}
使用 @BuilderParam
传递UI
@BuilderParam
注解用于在组件间传递UI组件,特别适用于需要传递复杂布局或多个UI组件的场景。
示例:创建一个可以接收自定义标题和内容的 CustomCard
组件。
CustomCard.ets:
@Component
struct CustomCard {
@BuilderParam title: () => any;
@BuilderParam content: () => any;
build() {
Column() {
this.title();
this.content();
}
}
}
使用 CustomCard
的父组件:
@Entry
@Component
struct HomePage {
build() {
CustomCard({
title: () => Text('Custom Title'),
content: () => Text('This is the custom content of the card.')
});
}
}
router-路由操作
页面跳转和后退
使用 router
对象可以在不同页面之间进行跳转和后退操作。
页面跳转
在页面跳转时,还可以使用 pushUrl
和 replaceUrl
方法。
pushUrl:用于在当前页面栈中推入新页面。
router.pushUrl('pages/DetailsPage');
replaceUrl:用于替换当前页面栈的顶层页面。
router.replaceUrl('pages/DetailsPage');
页面栈
页面栈管理用于维护页面的导航历史,允许应用程序在多个页面之间前进和后退。
页面栈内最多32个页面。
路由模式
ArkUI支持两种路由模式:standard
和 single
。
- standard模式:标准模式,每次跳转页面都会创建一个新的实例。
- single模式:单实例模式,每次跳转页面时,都会使用同一个页面实例,类似于单例模式。
设置路由模式:
router.setMode('standard'); // 或 'single'
路由传参
在页面跳转时,可以通过路由传递参数,这些参数可以在目标页面中获取。
传递参数:
router.push({
url: 'pages/DetailsPage',
params: {
id: 123,
name: 'example'
}
});
接收参数:
@Entry
@Component
struct DetailsPage {
@State id: number;
@State name: string;
onInit() {
const params = router.getParams();
this.id = params.id;
this.name = params.name;
}
build() {
Column() {
Text(`ID: ${this.id}`)
Text(`Name: ${this.name}`)
}
}
}
Stage模型
在ArkUI中,Stage模型是HarmonyOS的应用开发模型之一,它管理应用程序的生命周期和各个组件的交互。Stage模型由App、Module和Ability三部分组成。下面详细介绍它们之间的关系和生命周期。
App
功能:
- App是应用程序的根实体,代表整个应用的生命周期。它是应用的入口,管理应用的初始化、全局状态和资源。
生命周期:
onCreate()
: 应用启动时调用,用于进行初始化操作。onDestroy()
: 应用销毁时调用,用于进行清理操作。
示例:
@Entry
class MyApp extends App {
onCreate() {
console.log('Application created');
}
onDestroy() {
console.log('Application destroyed');
}
}
Module
功能:
- Module是应用程序的功能模块,一个应用可以包含多个模块,每个模块可以包含多个Ability。
- Module通过配置文件(config.json)定义,描述了模块的能力、权限等信息。
示例: config.json:
{
"module": {
"abilities": [
{
"name": "MainAbility",
"label": "Main Ability",
"icon": "$media:icon",
"type": "page"
}
],
"deviceType": ["phone", "tablet"],
"requiredPermissions": ["INTERNET"]
}
}
Ability
功能:
- Ability是应用程序的核心组件,代表应用的具体功能。主要有两种类型:Page Ability和Service Ability。
- Page Ability:用于管理界面和用户交互。
- Service Ability:用于后台任务和服务。
生命周期:
-
Page Ability:
onStart()
: Ability启动时调用。onActive()
: Ability进入前台时调用。onInactive()
: Ability进入后台时调用。onBackground()
: Ability完全进入后台时调用。onStop()
: Ability停止时调用。
-
Service Ability:
onStart()
: Ability启动时调用。onCommand()
: 接收并处理命令。onStop()
: Ability停止时调用。
示例: MainAbility.ts:
@Entry
class MainAbility extends Ability {
onStart(intent) {
console.log('MainAbility started');
}
onActive() {
console.log('MainAbility active');
}
onInactive() {
console.log('MainAbility inactive');
}
onBackground() {
console.log('MainAbility background');
}
onStop() {
console.log('MainAbility stopped');
}
}
关系和交互
- App 是整个应用的入口,管理全局状态和资源。
- Module 是应用的功能模块,包含一个或多个Ability。
- Ability 是具体功能的实现单元,可以是页面能力(Page Ability)或者服务能力(Service Ability)。
它们通过配置文件(如config.json)和生命周期方法进行交互和管理。
生命周期流程示例
一个典型的应用启动和运行流程如下:
- 应用启动:App的
onCreate()
方法被调用,应用初始化。 - Module加载:根据config.json加载模块和权限。
- Ability启动:Ability的
onStart()
方法被调用。 - Ability进入前台:Ability的
onActive()
方法被调用。 - Ability进入后台:Ability的
onInactive()
和onBackground()
方法被依次调用。 - Ability停止:Ability的
onStop()
方法被调用。 - 应用销毁:App的
onDestroy()
方法被调用,应用资源被清理。
通过这种层次化的设计,Stage模型使得应用的开发和管理更加模块化、清晰和高效。