性能提升的推薦方法

2024-02-07 12:51 更新

開(kāi)發(fā)者若使用低性能的代碼實(shí)現(xiàn)功能場(chǎng)景可能不會(huì)影響應(yīng)用的正常運(yùn)行,但卻會(huì)對(duì)應(yīng)用的性能造成負(fù)面影響。本章節(jié)列舉出了一些可提升性能的場(chǎng)景供開(kāi)發(fā)者參考,以避免應(yīng)用實(shí)現(xiàn)上帶來(lái)的性能劣化。

使用數(shù)據(jù)懶加載

開(kāi)發(fā)者在使用長(zhǎng)列表時(shí),如果直接采用循環(huán)渲染方式,如下所示,會(huì)一次性加載所有的列表元素,一方面會(huì)導(dǎo)致頁(yè)面啟動(dòng)時(shí)間過(guò)長(zhǎng),影響用戶體驗(yàn),另一方面也會(huì)增加服務(wù)器的壓力和流量,加重系統(tǒng)負(fù)擔(dān)。

  1. @Entry
  2. @Component
  3. struct MyComponent {
  4. @State arr: number[] = Array.from(Array(100), (v,k) =>k); //構(gòu)造0-99的數(shù)組
  5. build() {
  6. List() {
  7. ForEach(this.arr, (item: number) => {
  8. ListItem() {
  9. Text(`item value: ${item}`)
  10. }
  11. }, (item: number) => item.toString())
  12. }
  13. }
  14. }

上述代碼會(huì)在頁(yè)面加載時(shí)將100個(gè)列表元素全部加載,這并非我們需要的,我們希望從數(shù)據(jù)源中按需迭代加載數(shù)據(jù)并創(chuàng)建相應(yīng)組件,因此需要使用數(shù)據(jù)懶加載,如下所示:

  1. class BasicDataSource implements IDataSource {
  2. private listeners: DataChangeListener[] = []
  3. public totalCount(): number {
  4. return 0
  5. }
  6. public getData(index: number): any {
  7. return undefined
  8. }
  9. registerDataChangeListener(listener: DataChangeListener): void {
  10. if (this.listeners.indexOf(listener) < 0) {
  11. console.info('add listener')
  12. this.listeners.push(listener)
  13. }
  14. }
  15. unregisterDataChangeListener(listener: DataChangeListener): void {
  16. const pos = this.listeners.indexOf(listener);
  17. if (pos >= 0) {
  18. console.info('remove listener')
  19. this.listeners.splice(pos, 1)
  20. }
  21. }
  22. notifyDataReload(): void {
  23. this.listeners.forEach(listener => {
  24. listener.onDataReloaded()
  25. })
  26. }
  27. notifyDataAdd(index: number): void {
  28. this.listeners.forEach(listener => {
  29. listener.onDataAdd(index)
  30. })
  31. }
  32. notifyDataChange(index: number): void {
  33. this.listeners.forEach(listener => {
  34. listener.onDataChange(index)
  35. })
  36. }
  37. notifyDataDelete(index: number): void {
  38. this.listeners.forEach(listener => {
  39. listener.onDataDelete(index)
  40. })
  41. }
  42. notifyDataMove(from: number, to: number): void {
  43. this.listeners.forEach(listener => {
  44. listener.onDataMove(from, to)
  45. })
  46. }
  47. }
  48. class MyDataSource extends BasicDataSource {
  49. private dataArray: string[] = ['item value: 0', 'item value: 1', 'item value: 2']
  50. public totalCount(): number {
  51. return this.dataArray.length
  52. }
  53. public getData(index: number): any {
  54. return this.dataArray[index]
  55. }
  56. public addData(index: number, data: string): void {
  57. this.dataArray.splice(index, 0, data)
  58. this.notifyDataAdd(index)
  59. }
  60. public pushData(data: string): void {
  61. this.dataArray.push(data)
  62. this.notifyDataAdd(this.dataArray.length - 1)
  63. }
  64. }
  65. @Entry
  66. @Component
  67. struct MyComponent {
  68. private data: MyDataSource = new MyDataSource()
  69. build() {
  70. List() {
  71. LazyForEach(this.data, (item: string) => {
  72. ListItem() {
  73. Row() {
  74. Text(item).fontSize(20).margin({ left: 10 })
  75. }
  76. }
  77. .onClick(() => {
  78. this.data.pushData('item value: ' + this.data.totalCount())
  79. })
  80. }, item => item)
  81. }
  82. }
  83. }

上述代碼在頁(yè)面加載時(shí)僅初始化加載三個(gè)列表元素,之后每點(diǎn)擊一次列表元素,將增加一個(gè)列表元素。

設(shè)置List組件的寬高

在使用Scroll容器組件嵌套List組件加載長(zhǎng)列表時(shí),若不指定List的寬高尺寸,則默認(rèn)全部加載。

說(shuō)明

Scroll嵌套List時(shí):

  • List沒(méi)有設(shè)置寬高,會(huì)布局List的所有子組件。
  • List設(shè)置寬高,會(huì)布局List顯示區(qū)域內(nèi)的子組件。
  • List使用ForEach加載子組件時(shí),無(wú)論是否設(shè)置List的寬高,都會(huì)加載所有子組件。
  • List使用LazyForEach加載子組件時(shí),沒(méi)有設(shè)置List的寬高,會(huì)加載所有子組件,設(shè)置了List的寬高,會(huì)加載List顯示區(qū)域內(nèi)的子組件。
  1. class BasicDataSource implements IDataSource {
  2. private listeners: DataChangeListener[] = []
  3. public totalCount(): number {
  4. return 0
  5. }
  6. public getData(index: number): any {
  7. return undefined
  8. }
  9. registerDataChangeListener(listener: DataChangeListener): void {
  10. if (this.listeners.indexOf(listener) < 0) {
  11. console.info('add listener')
  12. this.listeners.push(listener)
  13. }
  14. }
  15. unregisterDataChangeListener(listener: DataChangeListener): void {
  16. const pos = this.listeners.indexOf(listener);
  17. if (pos >= 0) {
  18. console.info('remove listener')
  19. this.listeners.splice(pos, 1)
  20. }
  21. }
  22. notifyDataReload(): void {
  23. this.listeners.forEach(listener => {
  24. listener.onDataReloaded()
  25. })
  26. }
  27. notifyDataAdd(index: number): void {
  28. this.listeners.forEach(listener => {
  29. listener.onDataAdd(index)
  30. })
  31. }
  32. notifyDataChange(index: number): void {
  33. this.listeners.forEach(listener => {
  34. listener.onDataChange(index)
  35. })
  36. }
  37. notifyDataDelete(index: number): void {
  38. this.listeners.forEach(listener => {
  39. listener.onDataDelete(index)
  40. })
  41. }
  42. notifyDataMove(from: number, to: number): void {
  43. this.listeners.forEach(listener => {
  44. listener.onDataMove(from, to)
  45. })
  46. }
  47. }
  48. class MyDataSource extends BasicDataSource {
  49. private dataArray: Array<string> = new Array(100).fill('test')
  50. public totalCount(): number {
  51. return this.dataArray.length
  52. }
  53. public getData(index: number): any {
  54. return this.dataArray[index]
  55. }
  56. public addData(index: number, data: string): void {
  57. this.dataArray.splice(index, 0, data)
  58. this.notifyDataAdd(index)
  59. }
  60. public pushData(data: string): void {
  61. this.dataArray.push(data)
  62. this.notifyDataAdd(this.dataArray.length - 1)
  63. }
  64. }
  65. @Entry
  66. @Component
  67. struct MyComponent {
  68. private data: MyDataSource = new MyDataSource()
  69. build() {
  70. Scroll() {
  71. List() {
  72. LazyForEach(this.data, (item: string, index: number) => {
  73. ListItem() {
  74. Row() {
  75. Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
  76. }
  77. }
  78. })
  79. }
  80. }
  81. }
  82. }

因此,此場(chǎng)景下建議設(shè)置List子組件的寬高。

  1. class BasicDataSource implements IDataSource {
  2. private listeners: DataChangeListener[] = []
  3. public totalCount(): number {
  4. return 0
  5. }
  6. public getData(index: number): any {
  7. return undefined
  8. }
  9. registerDataChangeListener(listener: DataChangeListener): void {
  10. if (this.listeners.indexOf(listener) < 0) {
  11. console.info('add listener')
  12. this.listeners.push(listener)
  13. }
  14. }
  15. unregisterDataChangeListener(listener: DataChangeListener): void {
  16. const pos = this.listeners.indexOf(listener);
  17. if (pos >= 0) {
  18. console.info('remove listener')
  19. this.listeners.splice(pos, 1)
  20. }
  21. }
  22. notifyDataReload(): void {
  23. this.listeners.forEach(listener => {
  24. listener.onDataReloaded()
  25. })
  26. }
  27. notifyDataAdd(index: number): void {
  28. this.listeners.forEach(listener => {
  29. listener.onDataAdd(index)
  30. })
  31. }
  32. notifyDataChange(index: number): void {
  33. this.listeners.forEach(listener => {
  34. listener.onDataChange(index)
  35. })
  36. }
  37. notifyDataDelete(index: number): void {
  38. this.listeners.forEach(listener => {
  39. listener.onDataDelete(index)
  40. })
  41. }
  42. notifyDataMove(from: number, to: number): void {
  43. this.listeners.forEach(listener => {
  44. listener.onDataMove(from, to)
  45. })
  46. }
  47. }
  48. class MyDataSource extends BasicDataSource {
  49. private dataArray: Array<string> = new Array(100).fill('test')
  50. public totalCount(): number {
  51. return this.dataArray.length
  52. }
  53. public getData(index: number): any {
  54. return this.dataArray[index]
  55. }
  56. public addData(index: number, data: string): void {
  57. this.dataArray.splice(index, 0, data)
  58. this.notifyDataAdd(index)
  59. }
  60. public pushData(data: string): void {
  61. this.dataArray.push(data)
  62. this.notifyDataAdd(this.dataArray.length - 1)
  63. }
  64. }
  65. @Entry
  66. @Component
  67. struct MyComponent {
  68. private data: MyDataSource = new MyDataSource()
  69. build() {
  70. Scroll() {
  71. List() {
  72. LazyForEach(this.data, (item: string, index: number) => {
  73. ListItem() {
  74. Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
  75. }.width('100%')
  76. })
  77. }.width('100%').height(500)
  78. }.backgroundColor(Color.Pink)
  79. }
  80. }

使用條件渲染替代顯隱控制

如下所示,開(kāi)發(fā)者在使用visibility通用屬性控制組件的顯隱狀態(tài)時(shí),仍存在組件的重新創(chuàng)建過(guò)程,造成性能上的損耗。

  1. @Entry
  2. @Component
  3. struct MyComponent {
  4. @State isVisible: Visibility = Visibility.Visible;
  5. build() {
  6. Column() {
  7. Button("顯隱切換")
  8. .onClick(() => {
  9. if (this.isVisible == Visibility.Visible) {
  10. this.isVisible = Visibility.None
  11. } else {
  12. this.isVisible = Visibility.Visible
  13. }
  14. })
  15. Row().visibility(this.isVisible)
  16. .width(300).height(300).backgroundColor(Color.Pink)
  17. }.width('100%')
  18. }
  19. }

要避免這一問(wèn)題,可使用if條件渲染代替visibility屬性變換,如下所示:

  1. @Entry
  2. @Component
  3. struct MyComponent {
  4. @State isVisible: boolean = true;
  5. build() {
  6. Column() {
  7. Button("顯隱切換")
  8. .onClick(() => {
  9. this.isVisible = !this.isVisible
  10. })
  11. if (this.isVisible) {
  12. Row()
  13. .width(300).height(300).backgroundColor(Color.Pink)
  14. }
  15. }.width('100%')
  16. }
  17. }

使用Column/Row替代Flex

由于Flex容器組件默認(rèn)情況下存在shrink導(dǎo)致二次布局,這會(huì)在一定程度上造成頁(yè)面渲染上的性能劣化。

  1. @Entry
  2. @Component
  3. struct MyComponent {
  4. build() {
  5. Flex({ direction: FlexDirection.Column }) {
  6. Flex().width(300).height(200).backgroundColor(Color.Pink)
  7. Flex().width(300).height(200).backgroundColor(Color.Yellow)
  8. Flex().width(300).height(200).backgroundColor(Color.Grey)
  9. }
  10. }
  11. }

上述代碼可將Flex替換為Column、Row,在保證實(shí)現(xiàn)的頁(yè)面布局效果相同的前提下避免Flex二次布局帶來(lái)的負(fù)面影響。

  1. @Entry
  2. @Component
  3. struct MyComponent {
  4. build() {
  5. Column() {
  6. Row().width(300).height(200).backgroundColor(Color.Pink)
  7. Row().width(300).height(200).backgroundColor(Color.Yellow)
  8. Row().width(300).height(200).backgroundColor(Color.Grey)
  9. }
  10. }
  11. }

減少應(yīng)用滑動(dòng)白塊

應(yīng)用通過(guò)增大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ù)的例子:

  1. @Entry
  2. @Component
  3. struct MyComponent {
  4. private source: MyDataSource = new MyDataSource();
  5. build() {
  6. List() {
  7. LazyForEach(this.source, item => {
  8. ListItem() {
  9. Text("Hello" + item)
  10. .fontSize(50)
  11. .onAppear(() => {
  12. console.log("appear:" + item)
  13. })
  14. }
  15. })
  16. }.cachedCount(3) // 擴(kuò)大數(shù)值appear日志范圍會(huì)變大
  17. }
  18. }
  19. class MyDataSource implements IDataSource {
  20. data: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
  21. public totalCount(): number {
  22. return this.data.length
  23. }
  24. public getData(index: number): any {
  25. return this.data[index]
  26. }
  27. registerDataChangeListener(listener: DataChangeListener): void {
  28. }
  29. unregisterDataChangeListener(listener: DataChangeListener): void {
  30. }
  31. }

使用說(shuō)明:

cachedCount的增加會(huì)增大UI的cpu、內(nèi)存開(kāi)銷(xiāo)。使用時(shí)需要根據(jù)實(shí)際情況,綜合性能和用戶體驗(yàn)進(jìn)行調(diào)整。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)