作者:何贝
前言
鸿蒙3.0推出了一种新的开发方式ETS,本文通过采用ETS实现我们项目开发中比较常见的下拉选择菜单组件来初步了解和体验下ETS的开发规范和方法,主要用到的TS知识点有Flex布局、文本展示、样式绑定、图片引入、父子组件变量共享、参数传递、ForEach循环遍历、事件绑定。
实现效果

用到的装饰器
装饰器 |
装饰内容 |
说明 |
@Component |
struct |
结构体在装饰后具有基于组件的能力,需要实现build方法来更新UI。 |
@Entry |
struct |
组件被装饰后作为页面的入口,页面加载时将被渲染显示。 |
@Builder |
方法 |
在@Builder装饰的方法用通过声明式UI描述,可以在一个自定义组件内快速生成多个布局内容。 |
@Provide |
基本数据类型,类,数组 |
@Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。 |
@Consume |
基本数据类型,类,数组 |
@Consume装饰的变量在感知到@Provide装饰的变量更新后,会触发当前自定义组件的重新渲染。 |
创建ETS工程
- 在DevEco Studio中新建一个ETS工程,语言选择ets。



- 在entry -> src -> main -> ets -> default -> pages目录下新建自己的.ets文件,即可以开始编写ets代码。

功能分解
- 创建一个Flex布局的卡片
- 在卡片中填充内容
- 图片资源的引入,icon.png存在resources->base->media目录下
- 绑定点击事件
- 循环输出标签
- 父子组件共享变量
父组件中使用@Provide修饰变量,子组件使用@Consume修饰同一个变量名,即可以实现共享。
完整代码
export class ModeType {
id: number
name: string
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
@Component
struct ModeItem {
private mode: ModeType
@Consume modeId : number
@Builder renderModeItem(fontColor: string, bgColor: string, value: string) {
Flex({direction: FlexDirection.Column}) {
Flex(){
Text(value)
.fontSize(16)
.fontColor(fontColor)
}
.height('100%')
.width('100%')
.backgroundColor(bgColor)
.borderRadius(12)
.padding({
left: 14,
top: 14,
bottom: 14
})
if (this.mode.id != 5) {
Flex() {
Flex() {
}.height(1).width('100%').backgroundColor('#F3F3F3')
}
.padding({
left: 12,
right: 12
})
}
}
}
build() {
Flex(){
if (this.modeId == this.mode.id) {
this.renderModeItem('#0A59F7', '#E6EEFF', this.mode.name)
} else {
this.renderModeItem('rgba(0,0,0,0.9)', '', this.mode.name)
}
}.height(48)
}
}
@Component
struct SelectorList {
@Consume modesData: any
@Consume modeId: number
@Consume showSelector: boolean
build() {
Flex({direction: FlexDirection.Column, justifyContent: FlexAlign.Start}) {
ForEach(this.modesData, item => {
Flex(){
ModeItem({ mode: item })
}
.onClick(() => {
if (this.modeId !== item.id) {
this.modeId = item.id
console.info('this.modeId==='+this.modeId)
}
this.showSelector = false
})
}, item => item.id.toString())
}
.height(248)
.width('100%')
.backgroundColor('#fff')
.borderRadius(16)
.shadow({
radius: 50,
color: 'rgba(0,0,30,0.1500)'
})
.padding({
left: 4,
right: 4,
top: 4,
bottom: 4
})
.position({
x: 0,
y: -260
})
.zIndex(1)
}
}
@Entry
@Component
struct SelectorIndex {
@Provide showSelector: boolean = false
@Provide modesData: any = [{id: 1,name: '节能模式'},{id: 2,name: '强劲模式'},{id: 3,name: '深层模式'},{id: 4,name: '清洁模式'},{id: 5,name: '敏感模式'}]
@Provide modeId: number = 0
build() {
Flex({direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
Text('其他内容1').fontSize(16)
}.height(88).width('100%').backgroundColor('#fff').borderRadius(16)
Flex({direction: FlexDirection.Column, alignItems: ItemAlign.Center}) {
if (this.showSelector) {
SelectorList()
}
Flex({justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center}) {
Column() {
Text('模式选择')
.fontSize(16)
if (this.getSelectedText()) {
Text(this.getSelectedText())
.fontSize(12)
.alignSelf(ItemAlign.Start)
.fontColor('rgba(0, 0, 0, 0.6)')
.margin({
top: 2
})
}
}
Column() {
Flex(){
Image($r('app.media.icon'))
.objectFit(ImageFit.Contain)
}
.height(24)
.width(24)
}
}
.height(64)
.width('100%')
.backgroundColor('#fff')
.borderRadius(16)
.padding({
left: 16,
right: 16
})
.onClick(() => {
this.showSelector = !this.showSelector;
console.log('showSelector==='+this.showSelector)
})
}
.height(64)
.margin({
top: 12,
bottom: 12
})
Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
Text('其他内容2').fontSize(16)
}.height(88).width('100%').backgroundColor('#fff').borderRadius(16)
}
.height('100%')
.width('100%')
.backgroundColor('#f1f3f5')
.padding({
left: 12,
right: 12,
top: 16,
bottom: 16
})
.onClick(() => {
this.showSelector = false
})
}
getSelectedText() {
const selectedItem = this.modesData.find(item => {
return item.id == this.modeId
})
if (selectedItem) {
return selectedItem.name
}
return ''
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
总结
从上面的示例我们可以感受到基于TS扩展的声明式开发范式的方舟开发框架,采用链式调用的编程方式,可以更直观地描述UI界面,不必关心框架如何实现UI绘制和渲染,另外也提供了丰富的系统能力接口,真正实现极简高效开发。大家感兴趣的话可以通过TS开发官方文档进行详细的了解和学习。华为鸿蒙官方也发布了3 个超级实用的基于 TS 扩展的声明式开发范式示例,分别是eTSBuildCommonView 创建简单视图示例、eTSDefiningPageLayoutAndConnection 页面布局和连接示例、eTSDrawingAndAnimation 绘图和动画示例,可以下载代码体验和学习。
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
是ETS的内容!前排学习。
这娃好厉害啊,这娃真棒
赞,学习下
真棒,很详细,学习学习
看了下官方示例还是挺丰富的,期待ETS未来的发展
看这个面板是要弄个空调遥控器?
不错,TS确实简洁不少
看来TS确实要取代JS了
回家试试代码,学习一下
我运行这个代码,怎么报错了, 'SelectorList()' does not comply with the UI component syntax. <ArkTSCheck>