低性能的代碼實(shí)現(xiàn)功能場(chǎng)景可能不會(huì)影響應(yīng)用的正常運(yùn)行,但卻會(huì)對(duì)應(yīng)用的性能造成負(fù)面影響。本文列舉出了一些可提升性能的場(chǎng)景供參考。
使用數(shù)據(jù)懶加載
開發(fā)者在使用長(zhǎng)列表時(shí),如果直接采用循環(huán)渲染方式,如下所示,會(huì)一次性加載所有的列表元素,一方面會(huì)導(dǎo)致頁(yè)面啟動(dòng)時(shí)間過長(zhǎng),影響用戶體驗(yàn),另一方面也會(huì)增加服務(wù)器的壓力和流量,加重系統(tǒng)負(fù)擔(dān)。
@Entry
@Component
struct MyComponent {
@State arr: number[] = Array.from(Array(100), (v,k) =>k); //構(gòu)造0-99的數(shù)組
build() {
List() {
ForEach(this.arr, (item: number) => {
ListItem() {
Text(`item value: ${item}`)
}
}, (item: number) => item.toString())
}
}
}
上述代碼會(huì)在頁(yè)面加載時(shí)將100個(gè)列表元素全部加載,這并非我們需要的,我們希望從數(shù)據(jù)源中按需迭代加載數(shù)據(jù)并創(chuàng)建相應(yīng)組件,因此需要使用數(shù)據(jù)懶加載,如下所示:
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[] = ['item value: 0', 'item value: 1', 'item value: 2']
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() {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(20).margin({ left: 10 })
}
}
.onClick(() => {
this.data.pushData('item value: ' + this.data.totalCount())
})
}, item => item)
}
}
}

上述代碼在頁(yè)面加載時(shí)僅初始化加載三個(gè)列表元素,之后每點(diǎn)擊一次列表元素,將增加一個(gè)列表元素。
設(shè)置List組件的寬高
在使用Scroll容器組件嵌套List組件加載長(zhǎng)列表時(shí),若不指定List的寬高尺寸,則默認(rèn)全部加載。
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: Array<string> = new Array(100).fill('test')
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() {
Scroll() {
List() {
LazyForEach(this.data, (item: string, index: number) => {
ListItem() {
Row() {
Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
}
}
})
}
}
}
}
因此,此場(chǎng)景下建議設(shè)置List子組件的寬高。
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: Array<string> = new Array(100).fill('test')
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() {
Scroll() {
List() {
LazyForEach(this.data, (item: string, index: number) => {
ListItem() {
Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
}.width('100%')
})
}.width('100%').height(500)
}.backgroundColor(Color.Pink)
}
}
使用條件渲染替代顯隱控制
如下所示,開發(fā)者在使用visibility通用屬性控制組件的顯隱狀態(tài)時(shí),仍存在組件的重新創(chuàng)建過程,造成性能上的損耗。
@Entry
@Component
struct MyComponent {
@State isVisible: Visibility = Visibility.Visible;
build() {
Column() {
Button("顯隱切換")
.onClick(() => {
if (this.isVisible == Visibility.Visible) {
this.isVisible = Visibility.None
} else {
this.isVisible = Visibility.Visible
}
})
Row().visibility(this.isVisible)
.width(300).height(300).backgroundColor(Color.Pink)
}.width('100%')
}
}
要避免這一問題,可使用if條件渲染代替visibility屬性變換,如下所示:
@Entry
@Component
struct MyComponent {
@State isVisible: boolean = true;
build() {
Column() {
Button("顯隱切換")
.onClick(() => {
this.isVisible = !this.isVisible
})
if (this.isVisible) {
Row()
.width(300).height(300).backgroundColor(Color.Pink)
}
}.width('100%')
}
}
使用Column/Row替代Flex
由于Flex容器組件默認(rèn)情況下存在shrink導(dǎo)致二次布局,這會(huì)在一定程度上造成頁(yè)面渲染上的性能劣化。
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column }) {
Flex().width(300).height(200).backgroundColor(Color.Pink)
Flex().width(300).height(200).backgroundColor(Color.Yellow)
Flex().width(300).height(200).backgroundColor(Color.Grey)
}
}
}
上述代碼可將Flex替換為Column、Row,在保證實(shí)現(xiàn)的頁(yè)面布局效果相同的前提下避免Flex二次布局帶來的負(fù)面影響。
@Entry
@Component
struct MyComponent {
build() {
Column() {
Row().width(300).height(200).backgroundColor(Color.Pink)
Row().width(300).height(200).backgroundColor(Color.Yellow)
Row().width(300).height(200).backgroundColor(Color.Grey)
}
}
}
減少應(yīng)用滑動(dòng)白塊
應(yīng)用通過增大List/Grid控件的cachedCount參數(shù),調(diào)整UI的加載范圍。cachedCount表示屏幕外List/Grid預(yù)加載item的個(gè)數(shù)。
如果需要請(qǐng)求網(wǎng)絡(luò)圖片,可以在item滑動(dòng)到屏幕顯示之前,提前下載好內(nèi)容,從而減少滑動(dòng)白塊。
如下是使用cachedCount參數(shù)的例子:
@Entry
@Component
struct MyComponent {
private source: MyDataSource = new MyDataSource();
build() {
List() {
LazyForEach(this.source, item => {
ListItem() {
Text("Hello" + item)
.fontSize(50)
.onAppear(() => {
console.log("appear:" + item)
})
}
})
}.cachedCount(3) // 擴(kuò)大數(shù)值appear日志范圍會(huì)變大
}
}
class MyDataSource implements IDataSource {
data: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
public totalCount(): number {
return this.data.length
}
public getData(index: number): any {
return this.data[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
}
unregisterDataChangeListener(listener: DataChangeListener): void {
}
}
使用說明:
cachedCount的增加會(huì)增大UI的cpu、內(nèi)存開銷。使用時(shí)需要根據(jù)實(shí)際情況,綜合性能和用戶體驗(yàn)進(jìn)行調(diào)整。