1.list组件与listitem组件
<1>list组件
列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。
子组件
仅支持<list-item-group>和<list-item>。
属性
除支持通用属性外,还支持如下属性:
![](https://img-blog.csdnimg.cn/img_convert/8877aa20a80a10a2f3c5c012b13915a6.png)
样式
除支持通用样式外,还支持如下样式:
![](https://img-blog.csdnimg.cn/img_convert/a8445fd125c682a9ee47cad815b88151.png)
事件
除支持通用事件外,还支持如下事件:
![](https://img-blog.csdnimg.cn/img_convert/d43fa07eaebac82cadff8ffd3205c5ef.png)
方法
支持通用方法外,还支持如下方法:
![](https://img-blog.csdnimg.cn/img_convert/b4f91f61ae4c7152909e32fbbcdf99a2.png)
表1 currentOffset返回对象属性说明
![](https://img-blog.csdnimg.cn/img_convert/e41e43b783aef996992fcb9eeb049fb7.png)
示例
<!-- index.hml -->
<div class="container">
<list class="todo-wrapper">
<list-item for="{{todolist}}" class="todo-item">
<div style="flex-direction: column;align-items: center;justify-content: center;">
<text class="todo-title">{{$item.title}}</text>
<text class="todo-title">{{$item.date}}</text>
</div>
</list-item>
</list>
</div>
/* index.css */
.container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.todo-wrapper {
width: 100%;
height: 300px;
}
.todo-item {
width: 100%;
height: 120px;
justify-content:center;
}
.todo-title {
width: 100%;
height: 80px;
text-align: center;
}
// index.js
export default {
data: {
todolist: [{
title: '刷题',
date: '2021-12-31 10:00:00'
}, {
title: '看电影',
date: '2021-12-31 20:00:00'
}],
},
}
![](https://img-blog.csdnimg.cn/img_convert/af34252a49c3ce15ee1dfb03bad96a5e.png)
<2>listitem组件
<list>的子组件,用来展示列表具体item。由于父元素list组件的align-items默认样式为stretch,该组件宽度默认充满list组件。设置父元素list组件的align-items样式为非stretch来生效自定义宽度。
子组件
支持单个子组件。
属性
除支持通用属性外,还支持如下属性:
![](https://img-blog.csdnimg.cn/img_convert/dd5cd730063160a47180a961395bf8f3.png)
样式
除支持通用样式外,还支持如下样式:
![](https://img-blog.csdnimg.cn/img_convert/f1bc6450b955fe2214c59186958246a2.png)
事件
除支持通用事件外,还支持如下事件:
![](https://img-blog.csdnimg.cn/img_convert/c1ce52cb7f6c2f1843bbf4d4b3700b55.png)
方法
支持通用方法。
示例
详见List示例。
2.自定义组件
自定义组件的基本用法
自定义组件是用户根据业务需求,将已有的组件组合,封装成的新组件,可以在工程中多次调用,从而提高代码的可读性。自定义组件通过element引入到宿主页面,使用方法如下:
<element name='comp' src='../../common/component/comp.hml'></element>
<div>
<comp prop1='xxxx' @child1="bindParentVmMethod"></comp>
</div>
结合if-else使用自定义组件的示例,showComp1为true时显示自定义组件comp1,否则显示comp2:
<element name='comp1' src='../../common/component/comp1/comp1.hml'></element>
<element name='comp2' src='../../common/component/comp2/comp2.hml'></element>
<div>
<comp1 if="{{showComp1}}" prop1='xxxx' @child1="bindParentVmMethodOne"></comp1>
<comp2 else prop1='xxxx' @child1="bindParentVmMethodTwo"></comp2>
</div>
自定义组件的name属性指自定义组件名称(非必填),组件名称对大小写不敏感,默认使用小写。src属性指自定义组件hml文件路径(必填),若没有设置name属性,则默认使用hml文件名作为组件名。
自定义事件
父组件中绑定自定义子组件的事件使用(on|@)event-name="bindParentVmMethod"语法,子组件中通过this.$emit(‘eventName’, { params: ‘传递参数’ })触发事件并向上传递参数,父组件执行bindParentVmMethod方法并接收子组件传递的参数。
说明:
子组件中使用驼峰命名法命名的事件,在父组件中绑定时需要使用短横线分隔命名形式,例如:@children-event表示绑定子组件的childrenEvent事件。
示例1:无参数传递
子组件comp定义如下:
<!-- comp.hml -->
<div class="item">
<text class="text-style" onclick="childClicked">点击这里查看隐藏文本</text>
<text class="text-style" if="{{showObj}}">hello world</text>
</div>
/* comp.css */
.item {
width: 700px;
flex-direction: column;
height: 300px;
align-items: center;
margin-top: 100px;
}
.text-style {
font-weight: 500;
font-family: Courier;
font-size: 40px;
}
// comp.js
export default {
data: {
showObj: false,
},
childClicked () {
this.$emit('eventType1');
this.showObj = !this.showObj;
},
}
引入子组件comp的父组件示例如下:
<!-- xxx.hml -->
<element name='comp' src='../../common/component/comp.hml'></element>
<div class="container">
<comp @event-type1="textClicked"></comp>
</div>
/* xxx.css */
.container {
background-color: #f8f8ff;
flex: 1;
flex-direction: column;
align-content: center;
}
// xxx.js
export default {
textClicked () {}
}
示例2:有参数传递
子组件comp定义如下:
<!-- comp.hml -->
<div class="item">
<text class="text-style" onclick="childClicked">点击这里查看隐藏文本</text>
<text class="text-style" if="{{ showObj }}">hello world</text>
</div>
// comp.js
export default {
childClicked () {
this.$emit('eventType1', { text: '收到子组件参数' });
this.showObj = !this.showObj;
},
}
子组件向上传递参数text,父组件接收时通过e.detail来获取该参数:
<!-- xxx.hml -->
<element name='comp' src='../../common/comp/comp.hml'></element>
<div class="container">
<text>父组件:{{text}}</text>
<comp @event-type1="textClicked"></comp>
</div>
// xxx.js
export default {
data: {
text: '开始',
},
textClicked (e) {
this.text = e.detail.text;
},
}
![](https://img-blog.csdnimg.cn/img_convert/905df2e74c39e0066201c936b72fb820.png)
![](https://img-blog.csdnimg.cn/img_convert/8c8bfb6e7c9835331f00dffb9b565798.png)
自定义组件数据
自定义组件的js文件中可以通过声明data、props、computed等字段完成数据的定义、传递与处理,其中props与computed的具体使用请参考数据传递与处理章节。
表1 自定义组件数据
![](https://img-blog.csdnimg.cn/img_convert/1f6a3c237c95096e5e464b809de5baed.png)
Props
自定义组件可以通过props声明属性,父组件通过设置属性向子组件传递参数,props支持类型包括:String,Number,Boolean,Array,Object,Function。camelCase (驼峰命名法) 的 prop 名,在外部父组件传递参数时需要使用 kebab-case (短横线分隔命名) 形式,即当属性compProp在父组件引用时需要转换为comp-prop。给自定义组件添加props,通过父组件向下传递参数的示例如下:
<!-- comp.hml -->
<div class="item">
<text class="title-style">{{compProp}}</text>
</div>
// comp.js
export default {
props: ['compProp'],
}
<!-- xxx.hml -->
<element name='comp' src='../../common/component/comp/comp.hml'></element>
<div class="container">
<comp comp-prop="{{title}}"></comp>
</div>
说明:
自定义属性命名时禁止以on、@、on:、grab: 等保留关键字为开头。
添加默认值
子组件可以通过固定值default设置默认值,当父组件没有设置该属性时,将使用其默认值。此情况下props属性必须为对象形式,不能用数组形式,示例如下:
<!-- comp.hml -->
<div class="item">
<text class="title-style">{{title}}</text>
</div>
// comp.js
export default {
props: {
title: {
default: 'title',
},
},
}
本示例中加入了一个text组件显示标题,标题的内容是一个自定义属性,显示用户设置的标题内容,当用户没有设置时显示默认值title。在引用该组件时添加该属性的设置:
<!-- xxx.hml -->
<element name='comp' src='../../common/component/comp/comp.hml'></element>
<div class="container">
<comp title="自定义组件"></comp>
</div>
数据单向性
父子组件之间数据的传递是单向的,只能从父组件传递给子组件,子组件不能直接修改父组件传递下来的值,可以将props传入的值用data接收后作为默认值,再对data的值进行修改。
// comp.js
export default {
props: ['defaultCount'],
data() {
return {
count: this.defaultCount,
};
},
onClick() {
this.count = this.count + 1;
},
}
$watch 感知数据改变
如果需要观察组件中属性变化,可以通过$watch方法增加属性变化回调。使用方法如下:
// comp.js
export default {
props: ['title'],
onInit() {
this.$watch('title', 'onPropertyChange');
},
onPropertyChange(newV, oldV) {
console.info('title 属性变化 ' + newV + ' ' + oldV);
},
}
computed
自定义组件中经常需要在读取或设置某个属性时进行预先处理,提高开发效率,此种情况就需要使用computed计算属性。computed字段中可通过设置属性的getter和setter方法在属性读写的时候进行触发,使用方式如下:
// comp.js
export default {
props: ['title'],
data() {
return {
objTitle: this.title,
time: 'Today',
};
},
computed: {
message() {
return this.time + ' ' + this.objTitle;
},
notice: {
get() {
return this.time;
},
set(newValue) {
this.time = newValue;
},
},
},
onClick() {
console.info('get click event ' + this.message);
this.notice = 'Tomorrow';
},
}
这里声明的第一个计算属性message默认只有getter函数,message的值会取决于objTitle的值的变化。getter函数只能读取不能改变参数值,例如data中初始化定义的time,当需要赋值给计算属性的时候可以提供一个setter函数,如示例中的notice。
继承样式
说明:
从API version 9开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
自定义组件具有inherit-class属性,定义如下:
![](https://img-blog.csdnimg.cn/img_convert/1f42e627d30571c423151a05f48382fb.png)
可以通过设置inherit-calss属性来继承父组件的样式。
父组件的hml文件,其中自定义组件comp通过inherit-class属性来指定继承其父组件的样式,即parent-class1和parent-class2:
<!-- xxx.hml -->
<element name='comp' src='../../common/component/comp.hml'></element>
<div class="container">
<comp inherit-class="parent-class1 parent-class2" ></comp>
</div>
父组件的css文件:
/* xxx.css */
.parent-class1 {
background-color:red;
border:2px;
}
.parent-class2 {
background-color:green;
border:2px;
}
自定义组件的hml文件,其中parent-class1和parent-class2是从父组件继承的样式:
<!--comp.hml-->
<div class="item">
<text class="parent-class1">继承父组件的样式1</text>
<text class="parent-class2">继承父组件的样式2</text>
</div>
slot插槽
说明:
从API Version 7 开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
默认插槽
自定义组件中通过slot标签来承载父组件中定义的内容,使用slot标签可以更加灵活的控制自定义组件的内容元素,使用方式如下:
<!-- comp.hml -->
<div class="item">
<text class="text-style">下面使用父组件定义的内容</text>
<slot></slot>
</div>
引用该自定义组件方式如下:
<!-- xxx.hml -->
<element name='comp' src='../../common/component/comp.hml'></element>
<div class="container">
<comp>
<text class="text-style">父组件中定义的内容</text>
</comp>
</div>
具名插槽
当自定义组件中需要使用多个插槽时,可通过对插槽命名的方式进行区分,当填充插槽内容时,通过声明插槽名称,将内容加到对应的插槽中。
<!-- comp.hml -->
<div class="item">
<text class="text-style">下面使用父组件定义的内容</text>
<slot name="first"></slot>
<slot name="second"></slot>
</div>
引用该自定义组件方式如下:
<!-- xxx.hml -->
<element name='comp' src='../../common/component/comp.hml'></element>
<div class="container">
<comp>
<text class="text-style" slot="second">插入第二个插槽中</text>
<text class="text-style" slot="first">插入第一个插槽中</text>
</comp>
</div>
说明:
name 和 slot 属性不支持绑定动态数据。
生命周期定义
说明:
从API Version 5 开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
我们为自定义组件提供了一系列生命周期回调方法,便于开发者管理自定义组件的内部逻辑。生命周期主要包括:onInit,onAttached,onDetached,onLayoutReady,onDestroy,onShow和onHide。下面我们依次介绍一下各个生命周期回调的时机。
![](https://img-blog.csdnimg.cn/img_convert/3702fbc64636932ff42bd8ddc8596820.png)
示例
<!-- comp.hml -->
<div class="item">
<text class="text-style">{{ value }}</text>
</div>
//comp.js
export default {
data: {
value: "组件创建"
},
onInit() {
console.log("组件创建")
},
onAttached() {
this.value = "组件挂载",
console.log("组件挂载")
},
onShow() {
console.log("Page显示")
},
onHide() {
console.log("Page隐藏")
}
}
动态创建组件
提供在页面中动态添加组件,并为动态添加的组件设置属性与样式的能力。
说明:
从API version 8开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
createElement
createElement(tag: string): Element
创建一个组件对象。
参数:
![](https://img-blog.csdnimg.cn/img_convert/00e79d119524a9a736edc3ca0350ef66.png)
let newImage = dom.createElement('image')
setAttribute
setAttribute(name: string, value: string): void
动态设置组件的属性。
参数:
![](https://img-blog.csdnimg.cn/img_convert/25b89afb221f80220c6fab7ca9c88d3f.png)
newImage.setAttribute('src', 'common/testImage.jpg')
addChild
addChild(child: Element): void
将动态创建的组件添加到父组件当中。
参数:
![](https://img-blog.csdnimg.cn/img_convert/b54c012b9e305af1c5b710705a859e85.png)
newDiv.addChild(newImage)
示例
<!-- xxx.hml -->
<div class="container">
<div id="parentDiv" class="parent"></div>
<button onclick="appendDiv" class="btn">动态创建div</button>
<button onclick="appendImage" class="btn">动态创建image到创建的div中</button>
</div>
/* xxx.css */
.container {
flex-direction: column;
align-items: center;
width: 100%;
}
.parent {
flex-direction: column;
}
.btn {
width: 70%;
height: 60px;
margin: 15px;
}
// xxx.js
let newDiv = null
let newImage = null
export default {
appendDiv() {
let parent = this.$element('parentDiv')
newDiv = dom.createElement('div')
newDiv.setStyle('width', '150px')
newDiv.setStyle('height', '150px')
newDiv.setStyle('backgroundColor', '#AEEEEE')
newDiv.setStyle('marginTop', '15px')
parent.addChild(newDiv)
},
appendImage() {
newImage = dom.createElement('image')
newImage.setAttribute('src', 'common/testImage.jpg')
newImage.setStyle('width', '120px')
newImage.setStyle('height', '120px')
newDiv.addChild(newImage)
}
}
数据类型说明
长度类型
![](https://img-blog.csdnimg.cn/img_convert/ba0d3f1da9d7c28fec124aca34d37052.png)
颜色类型
![](https://img-blog.csdnimg.cn/img_convert/c242e5d08ef70ccd921377c6034412d8.png)
表1 当前支持的颜色枚举
![](https://img-blog.csdnimg.cn/img_convert/a7c71b50bac914ffb778696180b4e7ca.png)
3.渲染控制
ArkTS也提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。
条件渲染
使用if/else进行条件渲染。
说明:
if/else条件语句可以使用状态变量。
使用if/else可以使子组件的渲染依赖条件语句。
必须在容器组件内使用。
某些容器组件限制子组件的类型或数量,将if/else用于这些组件内时,这些限制将同样应用于if/else语句内创建的组件。例如,Grid容器组件的子组件仅支持GridItem组件,在Grid内使用if/else时,则if/else语句内也仅允许使用GridItem组件。
Column() {
if (this.count < 0) {
Text('count is negative').fontSize(14)
} else if (this.count % 2 === 0) {
Text('count is even').fontSize(14)
} else {
Text('count is odd').fontSize(14)
}
}
循环渲染
通过循环渲染(ForEach)从数组中获取数据,并为每个数据项创建相应的组件,可减少代码复杂度。
ForEach(
arr: any[],
itemGenerator: (item: any, index?: number) => void,
keyGenerator?: (item: any, index?: number) => string
)
参数:
![](https://img-blog.csdnimg.cn/img_convert/bd0c62c7efc1677ce4a253a5b3ca58b8.png)
说明:
ForEach必须在容器组件内使用。
生成的子组件应当是允许包含在ForEach父容器组件中的子组件。
允许子组件生成器函数中包含if/else条件渲染,同时也允许ForEach包含在if/else条件渲染语句中。
itemGenerator函数的调用顺序不一定和数组中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:
ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }),
item => Text(`${item.i}. item.data.label`),
item => item.data.id.toString())
示例
// xxx.ets
@Entry
@Component
struct MyComponent {
@State arr: number[] = [10, 20, 30]
build() {
Column({ space: 5 }) {
Button('Reverse Array')
.onClick(() => {
this.arr.reverse()
})
ForEach(this.arr, (item: number) => {
Text(`item value: ${item}`).fontSize(18)
Divider().strokeWidth(2)
}, (item: number) => item.toString())
}
}
}
![](https://img-blog.csdnimg.cn/img_convert/ff692f66b713bc8cace9950961cbbf4e.png)
数据懒加载
通过数据懒加载(LazyForEach)从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。
LazyForEach(
dataSource: IDataSource,
itemGenerator: (item: any) => void,
keyGenerator?: (item: any) => string
): void
interface IDataSource {
totalCount(): number;
getData(index: number): any;
registerDataChangeListener(listener: DataChangeListener): void;
unregisterDataChangeListener(listener: DataChangeListener): void;
}
interface DataChangeListener {
onDataReloaded(): void;
onDataAdd(index: number): void;
onDataMove(from: number, to: number): void;
onDataDelete(index: number): void;
onDataChange(index: number): void;
}
参数:
![](https://img-blog.csdnimg.cn/img_convert/fe06959557ebac23af49695473c7dc44.png)
IDataSource类型说明
![](https://img-blog.csdnimg.cn/img_convert/8cbd832a691d10a27aa5c8bce5073d59.png)
DataChangeListener类型说明
![](https://img-blog.csdnimg.cn/img_convert/610947662e71e9468619171544096229.png)
示例
// xxx.ets
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[] = ['/path/image0.png', '/path/image1.png', '/path/image2.png', '/path/image3.png']
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() {
Image(item).width(50).height(50)
Text(item).fontSize(20).margin({ left: 10 })
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// 每点击一次列表项,数据增加一项
this.data.pushData('/path/image' + this.data.totalCount() + '.png')
})
}, item => item)
}
}
}
说明:
LazyForEach必须在容器组件内使用,目前仅有List、Grid以及Swiper组件支持数据懒加载(即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。
生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
允许LazyForEach包含在if/else条件渲染语句中。
为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,仅当itemGenerator中创建的子组件内使用了状态变量时,才会触发组件刷新。
itemGenerator函数的调用顺序不一定和数据源中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:
LazyForEach(dataSource,
item => Text(`${item.i}. item.data.label`),
item => item.data.id.toString())
![](https://img-blog.csdnimg.cn/img_convert/cfc5a96516f93d95edf2e17f1ce5bffb.png)