1# 应用性能优化常见问题解决指导 2 3<!--Kit: Common--> 4<!--Subsystem: Demo&Sample--> 5<!--Owner: @mgy917--> 6<!--Designer: @jiangwensai--> 7<!--Tester: @Lyuxin--> 8<!--Adviser: @huipeizi--> 9 10## 概述 11 12本文总结了实际开发应用时常见的性能优化规范,配合举例实际开发中常见的正反例代码,帮助开发者解决大部分性能问题。 13 14### 性能规范总览目录 15|              <br />分类<br />                 |<br />高频程度 (5满分)<br />     | 规范(检查项) | 实操方法 | <br />代码示例<br />     | 16|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------:|:----------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------:| 17| 响应时延 / 完成时延 | 5 | 不建议在aboutToAppear(),aboutToDisappear()等生命周期中执行耗时操作。 | 排查所有的aboutToAppear和aboutToDisappear函数(或者通过Trace查看),查看是否有耗时操作,改为setTimeOut或者在TaskPool中执行。 | [代码示例](#不建议在abouttoappearabouttodisappear等生命周期中执行耗时操作) | 18| 响应时延 / 完成时延 | 5 | 不要在回调函数中执行耗时操作(ArkUI接口回调、网络访问回调、await等)。 | 排查所有的回调函数(或者通过Trace查看),尤其是ArkUI接口,网络回调函数,查看是否有耗时操作,是否使用了await操作,改为setTimeOut或者在TaskPool中执行。 | [代码示例](#不要在回调函数中执行耗时操作arkui接口回调网络访问回调await等) | 19| 响应时延 / 完成时延 / 帧率 | 5 | 列表场景未使用LazyForEach+组件复用+缓存列表项。 | 排查使用LazyForEach的代码,确认是否有使用组件复用(@Reusable)+缓存列表项(cachedCount)。 | [代码示例](#列表场景未使用lazyforeach组件复用缓存列表项) | 20| 完成时延 | 5 | Web未使用预连接,未提前初始化引擎。 | 在应用创建Ability的时候,在OnCreate阶段预先初始化内核,建议把引擎的初始化放在setTimeOut中。 | [代码示例](#web未使用预连接未提前初始化引擎) | 21| 响应时延 / 完成时延 | 5 | 高频接口中不要打印Trace和日志。 | 排查接口onTouch、onItemDragMove、onDragMove、onDidScroll、onMouse、onVisibleAreaChange、OnAreaChange、onActionUpdate、animator的onFrame、组件复用场景下的aboutToReuse,不建议在里面打印trace和日志。 | [代码示例](#高频接口中不要打印trace和日志) | 22| 完成时延 / 帧率 | 4 | 组件复用里面有if语句,但是未使用reuseId。 | 排查使用了@Reusable的自定义组件,查看build中给是否使用了if/else或ForEach等条件渲染语句,如果使用了,需要配合reuseId一起使用。 | [代码示例](#组件复用里面有if语句但是未使用reuseid) | 23| 响应时延 / 完成时延 | 4 | 不建议使用@Prop装饰器。 | 全局搜索@Prop并且替换 | [代码示例](#不建议使用prop装饰器) | 24| 响应时延 / 完成时延 | 3 | 避免在ResourceManager的getXXXSync接口入参中直接使用资源信息。 | 排查ResourceManager.getXXXSync接口,查看入参时需要使用getStringSync($r('app.media.icon').id)的形式,如果未使用需要整改。 | [代码示例](#避免在resourcemanager的getxxxsync接口入参中直接使用资源信息) | 25| 响应时延 / 完成时延 | 3 | 展示用的自定义组件(数据从父组件中获取,无独立数据处理)使用@Builder替换。 | 审视@Component标记的自定义组件,如果里面没有独立的生命周期处理逻辑,数据由父组件传递,建议@Builder替代。 | [代码示例](#展示用的自定义组件数据从父组件中获取无独立数据处理使用builder替换) | 26| 响应时延 / 完成时延 / 帧率 | 3 | 删除无具体逻辑的生命周期,ArkUI的函数回调等,删除冗余堵塞日志打印。 | 排查所有的aboutToAppear、aboutToDisappear等生命周期函数,排查ArkUI的回调函数,如果函数中无具体业务逻辑,例如只打印了日志,删除函数回调。 | [代码示例](#删除无具体逻辑的生命周期arkui的函数回调等删除冗余堵塞日志打印) | 27| 响应时延 / 完成时延 | 3 | 删除未关联组件的状态变量装饰器。 | 排查全局的状态变量装饰器,如果变量未关联组件,删除装饰器。 | [代码示例](#删除未关联组件的状态变量装饰器) | 28| 帧率 | 2 | crypto-js性能差。 | 排查buffer.from关键字,加密建议使用原生的cryptoFramework,然后将buffer替换为base64helper,性能提升10倍以上, 且数据量越大越明显。 | [代码示例](#crypto-js性能差) | 29| 响应时延 / 完成时延 | 1 | 不建议使用Marquee组件。 | 排查Marquee关键字,使用Text的跑马灯模式(TextOverflow.MARQUEE)替代。 | [代码示例](#不建议使用marquee组件) | 30| 完成时延 | 1 | 不能使用函数作为ArkUI组件的属性和组件复用的自定义组件的入参。 | 查看属性是否有xx()函数写法,确认函数/方法中是否有耗时操作,替换成变量。 | [代码示例](#不能使用函数作为arkui组件的属性和组件复用的自定义组件的入参) | 31| 完成时延 | 1 | 不建议使用.linearGradient颜色渐变属性。 | 排查linearGradient关键字,可以使用图片代替。 | [代码示例](#不建议使用lineargradient颜色渐变属性) | 32| 完成时延 / 帧率 | 1 | 不要在for/while循环中执行耗时操作。 | 排查for/while循环,查看里面是否有打印日志或者Trace。 | [代码示例](#不要在forwhile循环中执行耗时操作) | 33| 完成时延 / 帧率 | 1 | 变量初值不建议设置为undefined,需进行默认初始化。 | 例如number设置为0,string设置为空字符串等,这样在使用过程中更不需要增加额外判空。排查类中的变量,看看是否有初始化为undefined。 | [代码示例](#变量初值不建议设置为undefined需进行默认初始化) | 34 35## 性能优化规范 36 37### 不建议在aboutToAppear()、aboutToDisappear()等生命周期中执行耗时操作 38**类型** 39 40响应时延/完成时延 41 42**解决方法** 43 44排查所有的aboutToAppear和aboutToDisappear函数(或者通过Trace查看),查看是否有耗时操作,改为setTimeOut或者在TaskPool中执行。 45 46**反例** 47 48```typescript 49const LARGE_NUMBER = 1000000; 50 51@Entry 52@Component 53struct ViewA { 54 @State private text: string = ""; 55 private count: number = 0; 56 // 反例:在aboutToAppear接口中执行耗时操作,阻塞页面绘制。 57 aboutToAppear() { 58 // 耗时操作 59 this.computeTask(); 60 let context = this.getUIContext().getHostContext() as Context; 61 this.text = context.resourceManager.getStringSync($r('app.string.startup_text').id); 62 } 63 64 computeTask(): void { 65 this.count = 0; 66 while (this.count < LARGE_NUMBER) { 67 this.count++; 68 } 69 let context = this.getUIContext().getHostContext() as Context; 70 this.text = context.resourceManager.getStringSync($r('app.string.task_text').id); 71 } 72 73 build() { 74 // ... 75 } 76} 77``` 78**正例** 79 80```typescript 81@Entry 82@Component 83struct ViewB { 84 @State private text: string = ""; 85 private count: number = 0; 86 private readonly DELAYED_TIME: number = 2000; // 定时器设置延时2s 87 88 // 正例:在aboutToAppear接口中对耗时间的计算任务进行了异步处理。 89 aboutToAppear() { 90 // 耗时操作 91 this.computeTaskAsync(); // 异步任务 92 let context = this.getUIContext().getHostContext() as Context; 93 this.text = context.resourceManager.getStringSync($r('app.string.startup_text').id); 94 } 95 96 computeTask(): void { 97 this.count = 0; 98 while (this.count < LARGE_NUMBER) { 99 this.count++; 100 } 101 let context = this.getUIContext().getHostContext() as Context; 102 this.text = context.resourceManager.getStringSync($r('app.string.task_text').id); 103 } 104 105 // 运算任务异步处理 106 private computeTaskAsync(): void { 107 setTimeout(() => { 108 // 这里使用setTimeout来实现异步延迟运行 109 this.computeTask(); 110 }, this.DELAYED_TIME) 111 } 112 113 build() { 114 // ... 115 } 116} 117``` 118**高频程度&收益(5满分)** 119 1205 121 122### 不要在回调函数中执行耗时操作(ArkUI接口回调、网络访问回调、await等) 123**类型** 124 125响应时延/完成时延 126 127**解决方法** 128 129排查所有的回调函数(或者通过Trace查看),尤其是ArkUI接口,网络回调函数,查看是否有耗时操作,是否使用了await操作,改为setTimeOut或者在TaskPool中执行。 130 131**反例** 132 133```typescript 134import { http } from '@kit.NetworkKit'; 135 136async aboutToAppear() { 137 // ... 138 const b = await http.createHttp(); 139} 140``` 141**正例** 142 143```typescript 144aboutToAppear() { 145 // ... 146 // 在生命周期中,使用TaskPool加载和解析网络数据 147 this.requestByTaskPool(); 148} 149 150@Concurrent 151getInfoFromHttp(): string[] { 152 // 从网络加载数据 153 return http.request(); 154} 155 156requestByTaskPool(): void { 157 // 创建任务项 158 let task: taskpool.Task = new taskpool.Task(this.getInfoFromHttp); 159 try { 160 // 执行网络加载函数 161 taskpool.execute(task, taskpool.Priority.HIGH).then((res: string[]) => { 162 }); 163 } catch (err) { 164 logger.error(TAG, "failed, " + (err as BusinessError).toString()); 165 } 166} 167``` 168**高频程度&收益(5满分)** 169 1705 171 172### 列表场景未使用LazyForEach+组件复用+缓存列表项 173**类型** 174 175响应时延/完成时延/帧率 176 177**解决方法** 178 179排查使用LazyForEach的代码,确认是否有使用组件复用(@Reusable)+缓存列表项(cachedCount)。 180 181**反例** 182 183```typescript 184struct GoodView { 185 build() { 186 Grid() { 187 // 未使用LazyForEach+组件复用+缓存列表项 188 ForEach(this.GoodDataOne, (item, index) => { 189 GridItem() { 190 Column() { 191 Image(item.img) 192 .height(item.hei) 193 .width('100%') 194 .objectFit(ImageFit.Fill) 195 196 Text(item.introduce) 197 .fontSize(14) 198 .padding({ left: 5, right: 5 }) 199 .margin({ top: 5 }) 200 Row() { 201 Row() { 202 Text('¥') 203 .fontSize(10) 204 .fontColor(Color.Red) 205 .baselineOffset(-4) 206 Text(item.price) 207 .fontSize(16) 208 .fontColor(Color.Red) 209 Text(item.numb) 210 .fontSize(10) 211 .fontColor(Color.Gray) 212 .baselineOffset(-4) 213 .margin({ left: 5 }) 214 } 215 216 Image($r('app.media.photo63')) 217 .width(20) 218 .height(10) 219 .margin({ bottom: -8 }) 220 } 221 .width('100%') 222 .justifyContent(FlexAlign.SpaceBetween) 223 .padding({ left: 5, right: 5 }) 224 .margin({ top: 15 }) 225 } 226 .borderRadius(10) 227 .backgroundColor(Color.White) 228 .clip(true) 229 .width('100%') 230 .height(290) 231 } 232 }, (item) => JSON.stringify(item)) 233 } 234 } 235} 236``` 237**正例** 238 239```typescript 240// 组件复用 241@Reusable 242@Component 243struct GoodItems { 244 @State img: Resource = $r("app.media.photo61"); 245 @State webImg?: string = ''; 246 @State hei: number = 0; 247 @State introduce: string = ''; 248 @State price: string = ''; 249 @State numb: string = ''; 250 @LocalStorageLink('storageSimpleProp') simpleVarName: string = ''; 251 isOnclick: boolean = true; 252 index: number = 0; 253 controllerVideo: VideoController = new VideoController(); 254 255 aboutToReuse(params) 256 { 257 this.webImg = params.webImg; 258 this.img = params.img; 259 this.hei = params.hei; 260 this.introduce = params.introduce; 261 this.price = params.price; 262 this.numb = params.numb; 263 } 264 265 build() { 266 Grid(){ 267 // 懒加载 268 LazyForEach(this.GoodDataOne, (item, index) => { 269 GridItem() { 270 GoodItems({ 271 isOnclick:item.data.isOnclick, 272 img:item.data.img, 273 webImg:item.data.webImg, 274 hei:item.data.hei, 275 introduce:item.data.introduce, 276 price:item.data.price, 277 numb:item.data.numb, 278 index:index 279 }) 280 .reuseId(this.CombineStr(item.type)) 281 } 282 }, (item) => JSON.stringify(item)) 283 }.cachedCount(2) // 缓存列表项 284 } 285} 286``` 287**高频程度&收益(5满分)** 288 2895 290 291### Web未使用预连接,未提前初始化引擎 292**类型** 293 294完成时延 295 296**解决方法** 297 298在应用创建Ability的时候,在OnCreate阶段预先初始化内核,建议把引擎的初始化放在setTimeOut中。 299 300**反例** 301 302```typescript 303// Web组件引擎没有初始化,且沒有使用预连接 304export default class EntryAbility extends UIAbility { 305 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 306 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 307 } 308} 309controller: webview.WebviewController = new webview.WebviewController(); 310// ... 311Web({ src: 'https://www.example.com', controller: this.controller }) 312 313``` 314**正例** 315 316```typescript 317export default class EntryAbility extends UIAbility { 318 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 319 console.info("EntryAbility onCreate"); 320 // 在 Web 组件初始化之前,通过此接口加载 Web 引擎的动态库文件,以提高启动性能。 321 setTimeout(() => { 322 // 这里使用setTimeout来实现延迟运行 323 web_webview.WebviewController.initializeWebEngine(); 324 }, 200) 325 console.info("EntryAbility onCreate done"); 326 } 327} 328 329controller: webview.WebviewController = new webview.WebviewController(); 330// ... 331Web({ src: 'https://www.example.com', controller: this.controller }) 332 333``` 334**高频程度&收益(5满分)** 335 3365 337 338### 高频接口中不要打印Trace和日志 339**类型** 340 341响应时延/完成时延 342 343**解决方法** 344 345排查接口onTouch、onItemDragMove、onDragMove、onDidScroll、onMouse、onVisibleAreaChange、OnAreaChange、 346onActionUpdate、animator的onFrame、组件复用场景下的aboutToReuse,不建议在里面打印trace和日志。 347 348**反例** 349 350```typescript 351import { hiTraceMeter } from '@kit.PerformanceAnalysisKit'; 352 353@Component 354struct CounterOfOnDidScroll { 355 private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 356 357 build() { 358 Scroll() { 359 ForEach(this.arr, (item: number) => { 360 Text("ListItem" + item) 361 .width("100%") 362 .height("100%") 363 }, (item: number) => item.toString()) 364 } 365 .width('100%') 366 .height('100%') 367 .onDidScroll(() => { 368 hiTraceMeter.startTrace("ScrollSlide", 1002); 369 // 业务逻辑 370 // ... 371 // 在高频接口中不建议打印Trace和日志 372 hiTraceMeter.finishTrace("ScrollSlide", 1002); 373 }) 374 } 375``` 376**正例** 377 378```typescript 379@Component 380struct PositiveOfOnDidScroll { 381 private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 382 383 build() { 384 Scroll() { 385 List() { 386 ForEach(this.arr, (item: number) => { 387 ListItem() { 388 Text("TextItem" + item) 389 }.width("100%") 390 .height(100) 391 }, (item: number) => item.toString()) 392 } 393 .divider({ strokeWidth: 3, color: Color.Gray }) 394 } 395 .width('100%') 396 .height('100%') 397 .onDidScroll(() => { 398 // 业务逻辑 399 // ... 400 }) 401 } 402} 403``` 404**高频程度&收益(5满分)** 405 4064 407 408### 组件复用里面有if语句,但是未使用reuseId 409**类型** 410 411完成时延/帧率 412 413**解决方法** 414 415排查使用了@Reusable的自定义组件,查看build中给是否使用了if/else或ForEach等条件渲染语句,如果使用了,需要配合reuseId一起使用。 416 417**反例** 418 419```typescript 420@Component 421@Reusable 422export struct MockComplexSubBranch { 423 @State alignStyle: FlexAlign = FlexAlign.Center; 424 425 aboutToReuse(params: Record<string, number>): void { // 缓存复用组件,更新组件的状态变量 426 this.alignStyle = params.alignStyle; 427 } 428 429 build() { 430 Column() { 431 Column({ space: 5 }) { 432 Text('ComplexSubBranch not reusable') 433 .fontSize($r('app.integer.font_size_9')) 434 .fontColor($r('app.color.hint_txt_color')) 435 .width($r('app.string.layout_90_percent')) 436 } 437 } 438 } 439} 440 441import { MockComplexSubBranch } from './MockComplexSubBranch'; 442 443@Component 444export struct WithoutReuseId { 445 @State isAlignStyleStart: boolean = true; 446 447 build() { 448 Column() { 449 Button("Change FlexAlign") 450 .onClick(() => { 451 this.isAlignStyleStart = !this.isAlignStyleStart; 452 }) 453 Stack() { 454 if (this.isAlignStyleStart) { 455 MockComplexSubBranch({ alignStyle: FlexAlign.Start }); // 未使用reuseId 456 } else { 457 MockComplexSubBranch({ alignStyle: FlexAlign.End }); 458 } 459 } 460 } 461 } 462} 463``` 464**正例** 465 466```typescript 467@Component 468@Reusable 469// 添加Reusable装饰器,声明组件具备可复用的能力 470export struct MockComplexSubBranch { 471 @State alignStyle: FlexAlign = FlexAlign.Center; 472 473 aboutToReuse(params: Record<string, number>): void { 474 this.alignStyle = params.alignStyle; 475 } 476 477 build() { 478 Column() { 479 Column({ space: 5 }) { 480 Text('ComplexSubBranch reusable') 481 .fontSize($r('app.integer.font_size_9')) 482 .fontColor($r('app.color.hint_txt_color')) 483 .width($r('app.string.layout_90_percent')) 484 } 485 } 486 } 487} 488 489import { MockComplexReusableSubBranch } from './MockComplexReusableSubBranch'; 490 491@Component 492export struct WithReuseId { 493 @State isAlignStyleStart: boolean = true; 494 495 build() { 496 Column() { 497 Button("Change FlexAlign") 498 .onClick(() => { 499 this.isAlignStyleStart = !this.isAlignStyleStart; 500 }) 501 Stack() { 502 if (this.isAlignStyleStart) { 503 MockComplexSubBranch({ alignStyle: FlexAlign.Start }).reuseId("MockComplexSubBranchStart"); // 使用reuseId标识 504 } else { 505 MockComplexSubBranch({ alignStyle: FlexAlign.End }).reuseId("MockComplexSubBranchEnd"); 506 } 507 } 508 } 509 } 510} 511``` 512 513**高频程度&收益(5满分)** 514 5154 516 517### 不建议使用@Prop装饰器 518**类型** 519 520响应时延/完成时延 521 522**解决方法** 523 524全局搜索@Prop并且替换。 525 526**反例** 527 528```typescript 529@Observed 530class Book { 531 public c: number = 0; 532 533 constructor(c: number) { 534 this.c = c; 535 } 536} 537 538@Component 539struct PropChild { 540 @Prop testNum: Book; // @Prop装饰状态变量会深拷贝 541 542 build() { 543 Text(`PropChild testNum ${this.testNum.c}`) 544 } 545} 546 547@Entry 548@Component 549struct Parent1 { 550 @State testNum: Book[] = [new Book(1)]; 551 552 build() { 553 Column() { 554 Text(`Parent testNum ${this.testNum[0].c}`) 555 .onClick(() => { 556 this.testNum[0].c += 1; 557 }) 558 // PropChild没有改变@Prop testNum: Book的值,所以这时最优的选择是使用@ObjectLink 559 PropChild({ testNum: this.testNum[0] }) 560 } 561 } 562} 563``` 564**正例** 565 566```typescript 567@Observed 568class Book { 569 public c: number = 0; 570 571 constructor(c: number) { 572 this.c = c; 573 } 574} 575 576@Component 577struct PropChild { 578 @ObjectLink testNum: Book; // @ObjectLink装饰状态变量不会深拷贝 579 580 build() { 581 Text(`PropChild testNum ${this.testNum.c}`) 582 } 583} 584 585@Entry 586@Component 587struct Parent2 { 588 @State testNum: Book[] = [new Book(1)]; 589 590 build() { 591 Column() { 592 Text(`Parent testNum ${this.testNum[0].c}`) 593 .onClick(() => { 594 this.testNum[0].c += 1; 595 }) 596 // 当子组件不需要发生本地改变时,优先使用 @ObjectLink,因为@Prop是会深拷贝数据,具有拷贝的性能开销,所以这个时候@ObjectLink是比@Link和 @Prop更优的选择 597 PropChild({ testNum: this.testNum[0] }) 598 } 599 } 600} 601``` 602**高频程度&收益(5满分)** 603 6044 605 606### 避免在ResourceManager的getXXXSync接口入参中直接使用资源信息 607**类型** 608 609响应时延/完成时延 610 611**解决方法** 612 613排查ResourceManager.getXXXSync接口,查看入参时需要使用getStringSync($r('app.media.icon').id)的形式, 614如果未使用需要整改。 615 616**反例** 617 618```typescript 619this.context.resourceManager.getStringSync($r('app.string.test')); 620``` 621**正例** 622 623```typescript 624this.context.resourceManager.getStringSync($r('app.string.test').id); 625``` 626**高频程度&收益(5满分)** 627 6283 629 630### 展示用的自定义组件(数据从父组件中获取,无独立数据处理)使用@Builder替换 631**类型** 632 633响应时延/完成时延 634 635**解决方法** 636 637审视@Component标记的自定义组件,如果里面没有独立的生命周期处理逻辑,数据由父组件传递,建议@Builder替代。 638 639**反例** 640 641```typescript 642@Entry 643@Component 644struct CEMineButtomView { 645 build() { 646 View(); 647 } 648} 649 650@Component 651export struct ViewA { 652 build() { 653 Row() { 654 Text('- 到底了 -') 655 .fontSize(12) 656 .fontColor($r("app.color.color_1")) 657 }.justifyContent(FlexAlign.Center) 658 .width('100%') 659 .height(51) 660 .padding({ bottom: 21 }) 661 } 662} 663``` 664**正例** 665 666```typescript 667@Builder 668function viewB() { 669 Row() { 670 Text('- 到底了 -').fontSize(12) 671 .fontColor($r("app.color.color_1")) 672 } 673 .justifyContent(FlexAlign.Center) 674 .width('100%') 675 .height(51) 676 .padding({ bottom: 21 }) 677} 678 679@Entry 680@Component 681struct CEMineButtomView { 682 build() { 683 Column(){ 684 viewB() 685 }.width('100%') 686 } 687} 688``` 689**高频程度&收益(5满分)** 690 6913 692 693### 删除无具体逻辑的生命周期,ArkUI的函数回调等,删除冗余堵塞日志打印 694**类型** 695 696响应时延/完成时延/帧率 697 698**解决方法** 699 700排查所有的aboutToAppear、aboutToDisappear等生命周期函数,排查ArkUI的回调函数,如果函数中无具体业务逻辑, 701例如只打印了日志,删除函数回调。 702 703**反例** 704 705```typescript 706import { hilog } from '@kit.PerformanceAnalysisKit'; 707 708@Entry 709@Component 710struct ViewA { 711 aboutToAppear(): void { 712 hilog.info(0x101, 'tag', 'Index.ets aboutToAppear'); // 无具体业务逻辑的日志 713 } 714 715 aboutToDisappear(): void{ 716 hilog.info(0x101, 'tag', 'Index.ets aboutToDisappear'); // 无具体业务逻辑的日志 717 } 718 719 /** 720 * 弹窗函数 721 */ 722 showToast() { 723 this.getUIContext().getPromptAction().showToast({ 724 message: $r('app.string.water_mark_toast_message') 725 }) 726 } 727 728 build() { 729 Column(){ 730 Text('测试一下') 731 .onClick(() => { 732 this.showToast(); // 有业务逻辑的方法 733 }) 734 }.width('100%') 735 } 736} 737``` 738**正例** 739 740```typescript 741@Entry 742@Component 743struct ViewB { 744 /** 745 * 弹窗函数 746 */ 747 showToast() { 748 this.getUIContext().getPromptAction().showToast({ 749 message: $r('app.string.water_mark_toast_message') 750 }) 751 } 752 753 build() { 754 Column(){ 755 Text('测试一下') 756 .onClick(() => { 757 this.showToast(); // 有业务逻辑的方法 758 }) 759 }.width('100%') 760 } 761} 762``` 763**高频程度&收益(5满分)** 764 7653 766 767### 删除未关联组件的状态变量装饰器 768**类型** 769 770响应时延/完成时延 771 772**解决方法** 773 774排查全局的状态变量装饰器,如果变量未关联组件,删除装饰器。 775 776**反例** 777 778```typescript 779@Component 780struct ComponentA { 781 @State message: string = 'Hello World'; 782 @State textColor: string | Color = '#007DFF'; 783 @State bgcolor: string | Color = '#ffffff'; // 变量bgcolor是没有关联组件的 784 @State selectColor: string | Color = '#007DFF'; // 变量selectColor是没有关联组件的 785 786 build() { 787 Column(){ 788 Text(this.message) 789 .fontSize(50) 790 .fontWeight(FontWeight.Bold) 791 .fontColor(this.textColor) 792 } 793 } 794} 795``` 796**正例** 797 798```typescript 799@Component 800struct ComponentB { 801 @State message: string = 'Hello World'; 802 @State textColor: string | Color = '#007DFF'; 803 bgcolor: string | Color = '#ffffff'; // 变量bgcolor是有关联组件的 804 selectColor: string | Color = '#007DFF'; // 变量selectColor是有关联组件的 805 806 build() { 807 Column(){ 808 Text(this.message) 809 .fontSize(50) 810 .fontWeight(FontWeight.Bold) 811 .fontColor(this.selectColor) 812 .backgroundColor(this.bgcolor) 813 } 814 } 815} 816``` 817**高频程度&收益(5满分)** 818 8192 820 821### crypto-js性能差 822**类型** 823 824帧率 825 826**解决方法** 827 828排查buffer.from关键字,加密建议使用原生的cryptoFramework,然后将buffer替换为base64helper,性能提升10倍以上, 829且数据量越大越明显。 830 831**反例** 832 833```typescript 834new Uint8Array(buffer.from(str,'base64').buffer); 835``` 836**正例** 837 838```typescript 839let that = new util.Base64Helper(); 840let result = that.decodeSync(str); 841``` 842**高频程度&收益(5满分)** 843 8442 845 846### 不建议使用Marquee组件 847**类型** 848 849响应时延/完成时延 850 851**解决方法** 852 853排查Marquee关键字,使用Text的跑马灯模式(TextOverflow.MARQUEE)替代。 854 855**反例** 856 857```typescript 858struct ViewA { 859 build() { 860 Column() { 861 Marquee({ 862 start: this.start, 863 step: this.step, 864 loop: this.loop, 865 fromStart: this.fromStart, 866 src: this.src 867 }) 868 .width(360) 869 .height(80) 870 .fontColor('#FFFFFF') 871 .fontSize(48) 872 .fontWeight(700) 873 .backgroundColor('#182431') 874 .margin({ bottom: 40 }) 875 .onStart(() => { 876 console.info('Marquee animation complete onStart'); 877 }) 878 .onBounce(() => { 879 console.info('Marquee animation complete onBounce'); 880 }) 881 .onFinish(() => { 882 console.info('Marquee animation complete onFinish'); 883 }) 884 }.width("100%") 885 } 886} 887``` 888**正例** 889 890```typescript 891struct ViewB { 892 build(){ 893 Column(){ 894 Text(reply.user) 895 .maxLines(1) 896 .textOverflow({ overflow: TextOverflow.MARQUEE }) // 跑马灯模式 897 .width("30%") 898 }.width("100%") 899 } 900} 901``` 902**高频程度&收益(5满分)** 903 9041 905 906### 不能使用函数作为ArkUI组件的属性和组件复用的自定义组件的入参 907**类型** 908 909完成时延 910 911**解决方法** 912 913查看属性是否有xx()函数写法,确认函数/方法中是否有耗时操作,替换成变量。 914 915**反例** 916 917```typescript 918struct ViewA { 919 build() { 920 Column() { 921 List() { 922 LazyForEach(this.data, (item: string) => { 923 ListItem() { 924 // 此处sum参数是函数获取的,每次组件复用都会重复触发此函数的调用 925 ChildComponent({ desc: item, sum: this.count() }) 926 }.width('100%').height(100) 927 }, (item: string) => item) 928 } 929 } 930 } 931} 932``` 933**正例** 934 935```typescript 936struct ViewB { 937 @State sum: number = 0; 938 939 aboutToAppear(): void { 940 this.sum = this.count(); 941 } 942 943 build() { 944 Column() { 945 List() { 946 LazyForEach(this.data, (item: string) => { 947 ListItem() { 948 ChildComponent({ desc: item, sum: this.sum }) 949 }.width('100%').height(100) 950 }, (item: string) => item) 951 } 952 } 953 } 954} 955 956``` 957**高频程度&收益(5满分)** 958 9591 960 961### 不建议使用.linearGradient颜色渐变属性 962**类型** 963 964完成时延 965 966**解决方法** 967 968排查linearGradient关键字,可以使用图片代替。 969 970**反例** 971 972```typescript 973Row() 974 .linearGradient({ 975 angle: 90, 976 colors: [[0xff0000, 0.0], [0x0000ff, 0.3], [0xffff00, 1.0]] 977 }) 978``` 979**正例** 980 981```typescript 982Image($r('app.media.gradient_color')) 983``` 984**高频程度&收益(5满分)** 985 9861 987 988### 不要在for/while循环中执行耗时操作 989**类型** 990 991完成时延/帧率 992 993**解决方法** 994 995排查for/while循环,查看里面是否有打印日志或者Trace。 996 997**反例** 998 999```typescript 1000@Component 1001struct ViewA { 1002 @State message: string = ""; 1003 1004 build() { 1005 Column() { 1006 Button('点击打印日志').onClick(() => { 1007 for (let i = 0; i < 10; i++) { 1008 console.info(this.message); 1009 } 1010 }) 1011 } 1012 } 1013} 1014``` 1015**正例** 1016 1017```typescript 1018@Component 1019struct ViewB { 1020 @State message: string = ""; 1021 1022 build() { 1023 Column() { 1024 Button('点击打印日志').onClick(() => { 1025 let logMessage: string = this.message; 1026 for (let i = 0; i < 10; i++) { 1027 console.info(logMessage); // 状态变量需先赋值,再调用会优化性能 1028 } 1029 }) 1030 } 1031 } 1032} 1033``` 1034**高频程度&收益(5满分)** 1035 10361 1037 1038### 变量初值不建议设置为undefined,需进行默认初始化 1039**类型** 1040 1041完成时延 1042 1043**解决方法** 1044 1045例如number设置为0,string设置为空字符串等,这样在使用过程中更不需要增加额外判空。 1046排查类中的变量,看看是否有初始化为undefined。 1047 1048**反例** 1049 1050```typescript 1051@State channels?: Channels[] = undefined; 1052``` 1053**正例** 1054 1055```typescript 1056@State channels?: Channels[] = []; 1057``` 1058**高频程度&收益(5满分)** 1059 10601 1061 1062<!--no_check--> 1063 1064