基本语法
- 装饰器:如下图@Entry、@Component、@State都是装饰器。
- @Component表示自定义组件。
- @Entry表示该自定义组件的入口组件。
- @State表示组件中的状态变量,状态变量的变化会触发UI的刷新。
- UI描述:以声明式的方式来描述UI的结构,如上图build()方法中的代码。
- 自定义组件:可复用的UI组件,如上图被@Component装饰的struct App。
- 属性方法:通过链式调用使用更多属性,如上图.width('100%').height('100%')等。
- 事件方法:通过链式调用使用更多事件逻辑,如上图中的.onClick(()=>{ })等。
@Entry
@Component
struct App {
@State myText: string = 'HarmonyOS4.0';
build() {
Column() {
Text(`${this.myText}`).fontSize(32).fontWeight(800)
Button('点击').onClick(() => {
this.myText = '欢迎使用鸿蒙'
}).width(100).margin({top:20})
}.width('100%').height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
}
}
创建自定义组件
注意:@Entry装饰的组件,其build函数下的根结点必须为容器组件,并且forEach不能作为根节点。
@Component装饰的组件,其build函数下的根结点可以为非容器组件,并且forEach不能作为根节点。
1、在入口组件内部使用自定义组建
@Component
struct ExanpleComponent {
@State textValue: string = '组件状态'
build() {
Text(this.textValue)
.fontSize(32)
.fontWeight(600)
.fontColor('#e33')
.onClick(() => {
this.textValue = '组件状态变化'
})
}
}
@Entry
@Component
struct App {
@State myText: string = 'HarmonyOS4.0';
build() {
Column() {
ExanpleComponent()
Text(`${this.myText}`)
.fontSize(32)
.fontWeight(800)
Button('点击')
.width(100)
.margin({ top: 20 })
.onClick(() => {
this.myText = '欢迎使用鸿蒙'
})
}.width('100%').height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
}
}
2、从外部导入使用自定义组建
Index.ets @Entry入口文件
Example.ets @Component自定义组件文件
生命周期(页面和自定义组件)
1、页面生命周期,即被@Entry修饰的组件生命周期
- onPageShow:页面每次显示时触发。
- onPageHide:页面每次隐藏时触发。
- onBackPress:当用户点击返回按钮时出发。
2、组件生命周期,即被@Component修饰的组件生命周期
- aboutToAppear:组件刚出现的时候回调该接口,具体为在创建自定义组件实例后早build函数前。
- aboutToDisappear:在自定义组件销毁时执行。
@Styles
注意:@Style不能有参数。
// 定义全局@style样式
@Styles function publicStyle() {
.width('100%')
.height('100%')
.backgroundColor('#3ce')
}
@Entry
@Component
struct App {
@State myText: string = 'HarmonyOS4.0';
// 定义局部@style样式
@Styles internalStyle(){
.width(200)
.margin({ top: 20 })
}
build() {
Column() {
Button(this.myText)
.internalStyle()
.fontSize(20)
.onClick(() => {
this.myText = '欢迎使用鸿蒙'
})
}.publicStyle().justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
}
}
@Extend
概念
- 和@Style不同,@Extend仅支持定义在全局,不支持在组件内部定义。
- 和@Style不同,@Extend装饰的方法支持参数,可以在调用时传递参数
// 定义全局@Extend样式
@Extend(Column) function publicStyle(width:string,height:string) {
.width(width)
.height(height)
.backgroundColor('#3ae')
}
@Entry
@Component
struct App {
@State myText: string = 'HarmonyOS4.0';
build() {
Column() {
Button(this.myText)
.fontSize(20)
.onClick(() => {
this.myText = '欢迎使用鸿蒙'
})
}.publicStyle('100%','100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
}
}
stateStyles多态样式
概述
stateStyles是属性方法,类似于css伪类,ArkUI提供了以下四种状态:
- focused:获焦状态
- normal:正常状态
- pressed:按压状态
- disabled:不可用状态
@Styles function focusedStyle() {
.backgroundColor('red')
}
@Entry
@Component
struct App {
@State myText: string = 'HarmonyOS4.0';
@Styles pressedStyle() {
.backgroundColor('green')
}
build() {
Column() {
Button(this.myText)
.fontSize(20)
.stateStyles({
focused: focusedStyle,
pressed: this.pressedStyle,
normal: {
.backgroundColor('blue')
},
})
.onClick(() => {
this.myText = '欢迎使用鸿蒙'
})
}.width('100%').height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
}
}
@State状态管理
概念
- 状态变量:被@State装饰的变量,状态变量的状态值的改变会引起UI的渲染更新。
- 常规变量:没有被@State装饰的变量,它的改变永远不会引起UI的渲染更新。
@Component
struct MyComponent {
@State count: number = 0;
private increaseBy: number = 1;
build() {
}
}
@Component
struct Parent {
build() {
Column() {
// 从父组件初始化,覆盖本地定义的默认值
MyComponent({ count: 1, increaseBy: 2 })
}
}
}
@Prop父子单项同步(父传子)
概述
- 单项同步:对父组件的状态变量值的修改,将同步到子元素@Prop装饰的变量,子组件@Prop变量的修改不会同步到父组件的状态变量上。
注意:@props装饰器不能在@Entry装饰的自定义组件中使用。
@Component
struct Child {
@Prop count: number;
build() {
Column() {
Text(`子组件: ${this.count} `)
// @Prop装饰的变量不会同步给父组件
Button(`子组件-1`).onClick(() => {
this.count -= 1;
}).margin({ top: 24 })
}
}
}
@Entry
@Component
struct Parent {
@State num: number = 0;
build() {
Column() {
Text(`父组件:${this.num}`)
// 父组件的修改会同步给子组件
Button(`父组件+1`).onClick(() => {
this.num += 1;
}).margin({ top: 24 })
// 子组件
Child({ count: this.num }).margin({ top: 24 })
}.width('100%').justifyContent(FlexAlign.Center).margin({ top: 24 })
}
}
@Link父子双向同步(父传子、子传父)
概述
- 双向同步:对父组件的状态变量值的修改,将同步到子元素@Link装饰的变量,子组件@Link变量也将同步到父组件的状态变量上。
注意:@Link装饰器不能在@Entry装饰的自定义组件中使用。
@Component
struct Child {
@Link items: number[];
build() {
Column() {
Button(`子元素修改数组`).onClick(() => {
this.items = [100, 200, 300];
})
ForEach(this.items, item => {
Text(`${item}`)
})
}
}
}
@Entry
@Component
struct Parent {
@State arr: number[] = [1, 2, 3];
build() {
Column() {
Button(`父元素修改数组`).onClick(() => {
this.arr = [5, 6, 7];
})
ForEach(this.arr,
item => {
Text(`${item}`)
}
)
Child({ items: $arr })
}
}
}
@Provide和@Consume后代组件双向同步(跨组建传参)
概述
- 双向同步:@Provide与@Consume,应用于后代组件的数据双向同步,应用于数据在多个层级之间传递,实现跨层级传递。
- 介绍:@Provide装饰的变量是在祖先节点中,是变量的提供者,@Consume装饰的变量是在后代组件中,是变量的消费者。
注意:@Provide与@Consume装饰的变量必须通过相同的属性名进行同步数据。
@Component
struct CompD {
// @Consume装饰的变量通过相同的属性名绑定其祖先组件CompA内的@Provide装饰的变量
@Consume publicValue: number;
build() {
Column() {
Text(`D组件${this.publicValue}`)
Button(`D组件按钮-1`)
.onClick(() => this.publicValue -= 1)
}
.width('50%')
}
}
@Component
struct CompC {
build() {
CompD()
}
}
@Component
struct CompB {
build() {
CompC()
}
}
@Entry
@Component
struct CompA {
// @Provide装饰的变量publicValue由入口组件CompA提供其后代组件
@Provide publicValue: number = 0;
build() {
Column() {
Text(`A组件${this.publicValue}`)
Button(`A组件按钮+1`)
.onClick(() => this.publicValue += 1)
CompB()
}
}
}
@Observed和@ObjectLink多层嵌套双向同步
概述
- 上文所述的装饰器只能观察到一层的变化,对于多层嵌套的情况,比如二维数组、多维对象,它们其他层级的变化是无法被观察的,因此引出了@Observed和@ObjectLink装饰器。
注意:
- @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
- @ObjectLink装饰的变量不能被赋值。
// 开发进行中...
@Watch监听状态的变化
概述
- LocalStorage是页面级的UI存储,通过@Entry装饰器接收的参数可以在页面内共享一个LocalStorage实例。
@Entry
@Component
struct CompA {
@Watch('change') @State count: number = 1
change() {
console.log(`状态变化${this.count}`)
}
build() {
Column() {
Button(`点击状态变化${this.count}`)
.onClick(() => {
this.count++
})
}
}
}
LocalStorage页面级UI状态存储
概述
- LocalStorage是页面级的UI存储,通过@Entry装饰器接收的参数可以在页面内共享一个LocalStorage实例。
- 被@Entry装饰的@Component,可以被分配一个LocalStorage实例,此组件的所有子组件都将获取对该LocalStorage实例的访问权限,一个LocalStorage实例在组建数上可以分配给多个组件。
- 被@Component装饰的组件最多可访问一个LocalStorage实例和AppStorage。
- LocalStorage根据与@Component装饰的组件的同步类型不同,提供了两个装饰器:
- @LocalStorageProp:@LocalStorageProp装饰的变量与LocalStorage中给予属性建立的是单向同步关系。
- @LocalStorageLink:@LocalStorageLink装饰的变量与LocalStorage中给予属性建立的是双向向同步关系。
注意:LocalStorage创建后,命名属性的类型不可更改
1、@LocalStorageProp —— 单项数据同步
// import { Child } from '../components/Example'
let storage = new LocalStorage({ 'PropA': 47 });
@Component
struct Child {
@LocalStorageProp('PropA') num2: number = 1;
build() {
// 点击后,只改变当前组件显示的num2,不会同步到LocalStorage中
Button(`子组件${this.num2}`)
.onClick(() => {
this.num2 += 1;
})
}
}
@Entry(storage)
@Component
struct CompA {
@LocalStorageProp('PropA') num1: number = 1;
build() {
Column({ space: 15 }) {
Child()
// 点击后,只改变当前组件显示的num1,不会同步到LocalStorage中
Button(`父组件 ${this.num1}`)
.onClick(() => this.num1 += 1)
}
}
}
2、@LocalStorageLink —— 双项数据同步
// import { Child } from '../components/Example'
let storage = new LocalStorage({ 'PropA': 47 });
@Component
struct Child {
@LocalStorageLink('PropA') num2: number = 1;
build() {
Button(`子组件${this.num2}`)
.onClick(() => {
this.num2 += 1;
})
}
}
@Entry(storage)
@Component
struct CompA {
@LocalStorageLink('PropA') num1: number = 1;
build() {
Column({ space: 15 }) {
Child()
Button(`父组件 ${this.num1}`)
.onClick(() => this.num1 += 1)
}
}
}
AppStorage全局UI状态存储
概述
- AppStorage是应用全局的状态存储,和LocalStorage不同的是LocalStorage是页面级的,用于页面内的数据共享,而AppStorage是应用于全局状态的共享。
- AppStorage中也提供了两种同步类型:
- @StorageProp:@StorageProp(key)是和AppStorage中key对应的属性建立单向数据同步。
- @StorageLink:@StorageLink(key)是和AppStorage中key对应的属性建立双向数据同步。
1、@StorageProp —— 单项数据同步
AppStorage.SetOrCreate('PropA', 47);
@Component
struct Child {
@StorageProp('PropA') num2: number = 1;
build() {
Button(`子组件${this.num2}`)
.onClick(() => {
this.num2 += 1;
})
}
}
@Entry()
@Component
struct CompA {
@StorageProp('PropA') num1: number = 1;
build() {
Column({ space: 20 }) {
Child()
Button(`AppStorage ${this.num1}`)
.onClick(() => this.num1 += 1)
}
}
}
2、@StorageLink —— 双项数据同步
AppStorage.SetOrCreate('PropA', 47);
@Entry()
@Component
struct CompA {
@StorageLink('PropA') storLink: number = 1;
build() {
Column({ space: 20 }) {
Button(`AppStorage ${this.storLink}`)
.onClick(() => this.storLink += 1)
}
}
}
总结:LocalStorage和AppStorage都是运行时内存的状态存储。
PersistentStorage持久化UI状态存储
概述
- LocalStorage和AppStorage都是运行时的内存,在应用退出再次启动后,依然能保存选定的结果,这就需要用到PersistentStorage。
- PersistentStorage将选定的AppStorage属性保留在设备磁盘上。
PersistentStorage.PersistProp('aProp', 47);
@Entry
@Component
struct Index {
@StorageLink('aProp') num: number = 48
build() {
Row() {
Column() {
// 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
Button(`${this.num}`)
.onClick(() => {
this.num += 1;
})
}
}
}
}
if/else
概述
- 条件渲染使用if/else渲染对应状态下的ui内容。
- 鸿蒙开发推荐使用if/else,不推荐使用switch。
ForEach
概述
- 循环渲染,需要与容器组件配合使用。
@Entry
@Component
struct Parent {
@State simpleList: Array<string> = ['one', 'two', 'three'];
build() {
Row() {
Column() {
ForEach(this.simpleList, // 数据源
(item: string) => {
Text(item).fontSize(32) // 组件生成函数
},
(item: string) => item // 键值生成函数:为数据源arr的每个数组项生成唯一且持久的键值。
)
}
}
}
}
LazyForEach
概述
- LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
- LazyForEach必须在容器组件内使用,目前仅有List、Grid以及Swiper组件支持数据懒加载,其他组件仍然是一次性加载所有的数据。
- LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = []
public totalCount(): number {
return 0
}
public getData(index: number): any {
return undefined
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener')
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener')
this.listeners.splice(pos, 1)
}
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index)
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index)
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index)
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to)
})
}
}
class MyDataSource extends BasicDataSource {
// 初始化数据列表
private dataArray: string[] = ['demo1', 'demo2', 'demo3', 'demo4']
public totalCount(): number {
return this.dataArray.length
}
public getData(index: number): any {
return this.dataArray[index]
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data)
this.notifyDataAdd(index)
}
public pushData(data: string): void {
this.dataArray.push(data)
this.notifyDataAdd(this.dataArray.length - 1)
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource()
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(20).margin({ left: 10 })
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// 每点击一次列表项,数据增加一项
this.data.pushData(`demoVale:${this.data.totalCount()}`)
})
}, item => item)
}.backgroundColor('red')
}
}
应用/组件配置
概述
- 应用图标、应用标签和入口图标、入口标签的配置,分别对应app.json5配置文件和module.json5配置文件文件中的icon和label标签。
- 应用图标和标签是在设置应用中使用,例如设置应用中的应用列表。
- 入口图标是应用安装完成后在设备桌面上中使用。
1、应用配置
2、入口配置