1# 自定义组件冻结功能 2 3自定义组件处于非激活状态时,状态变量将不响应更新,即@Watch不会调用,状态变量关联的节点不会刷新。通过freezeWhenInactive属性来决定是否使用冻结功能,不传参数时默认不使用。支持的场景有:页面路由,TabContent,LazyforEach,Navigation。 4 5 6> **说明:** 7> 8> 从API version 11开始,支持自定义组件冻结功能。 9 10## 当前支持的场景 11 12### 页面路由 13 14- 当页面A调用router.pushUrl接口跳转到页面B时,页面A为隐藏不可见状态,此时如果更新页面A中的状态变量,不会触发页面A刷新。 15 16- 当应用退到后台运行时无法被冻结。 17 18页面A: 19 20```ts 21import router from '@ohos.router'; 22 23@Entry 24@Component({ freezeWhenInactive: true }) 25struct FirstTest { 26 @StorageLink('PropA') @Watch("first") storageLink: number = 47; 27 28 first() { 29 console.info("first page " + `${this.storageLink}`) 30 } 31 32 build() { 33 Column() { 34 Text(`From fist Page ${this.storageLink}`).fontSize(50) 35 Button('first page storageLink + 1').fontSize(30) 36 .onClick(() => { 37 this.storageLink += 1 38 }) 39 Button('go to next page').fontSize(30) 40 .onClick(() => { 41 router.pushUrl({ url: 'pages/second' }) 42 }) 43 } 44 } 45} 46``` 47 48页面B: 49 50```ts 51import router from '@ohos.router'; 52 53@Entry 54@Component({ freezeWhenInactive: true }) 55struct SecondTest { 56 @StorageLink('PropA') @Watch("second") storageLink2: number = 1; 57 58 second() { 59 console.info("second page: " + `${this.storageLink2}`) 60 } 61 62 build() { 63 Column() { 64 65 Text(`second Page ${this.storageLink2}`).fontSize(50) 66 Button('Change Divider.strokeWidth') 67 .onClick(() => { 68 router.back() 69 }) 70 71 Button('second page storageLink2 + 2').fontSize(30) 72 .onClick(() => { 73 this.storageLink2 += 2 74 }) 75 76 } 77 } 78} 79``` 80 81在上面的示例中: 82 831.点击页面A中的Button “first page storLink + 1”,storLink状态变量改变,@Watch中注册的方法first会被调用。 84 852.通过router.pushUrl({url: 'pages/second'}),跳转到页面B,页面A隐藏,状态由active变为inactive。 86 873.点击页面B中的Button “this.storLink2 += 2”,只回调页面B@Watch中注册的方法second,因为页面A的状态变量此时已被冻结。 88 894.点击“back”,页面B被销毁,页面A的状态由inactive变为active,重新刷新在inactive时被冻结的状态变量,页面A@Watch中注册的方法first被再次调用。 90 91 92### TabContent 93 94- 对Tabs中当前不可见的TabContent进行冻结,不会触发组件的更新。 95 96- 需要注意的是:在首次渲染的时候,Tab只会创建当前正在显示的TabContent,当切换全部的TabContent后,TabContent才会被全部创建。 97 98```ts 99@Entry 100@Component 101struct TabContentTest { 102 @State @Watch("onMessageUpdated") message: number = 0; 103 104 onMessageUpdated() { 105 console.info(`TabContent message callback func ${this.message}`) 106 } 107 108 build() { 109 Row() { 110 Column() { 111 Button('change message').onClick(() => { 112 this.message++ 113 }) 114 115 Tabs() { 116 TabContent() { 117 FreezeChild({ message: this.message }) 118 }.tabBar('one') 119 120 TabContent() { 121 FreezeChild({ message: this.message }) 122 }.tabBar('two') 123 } 124 } 125 .width('100%') 126 } 127 .height('100%') 128 } 129} 130 131@Component({ freezeWhenInactive: true }) 132struct FreezeChild { 133 @Link @Watch("onMessageUpdated") message: number 134 private index: number = 0 135 136 onMessageUpdated() { 137 console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`) 138 } 139 140 build() { 141 Text("message" + `${this.message}, index: ${this.index}`) 142 .fontSize(50) 143 .fontWeight(FontWeight.Bold) 144 } 145} 146``` 147 148在上面的示例中: 149 1501.点击“change message”更改message的值,当前正在显示的TabContent组件中的@Watch中注册的方法onMessageUpdated被触发。 151 1522.点击“two”切换到另外的TabContent,TabContent状态由inactive变为active,对应的@Watch中注册的方法onMessageUpdated被触发。 153 1543.再次点击“change message”更改message的值,仅当前显示的TabContent子组件中的@Watch中注册的方法onMessageUpdated被触发。 155 156 157 158 159### LazyforEach 160 161- 对LazyforEach中缓存的自定义组件进行冻结,不会触发组件的更新。 162 163```ts 164// Basic implementation of IDataSource to handle data listener 165class BasicDataSource implements IDataSource { 166 private listeners: DataChangeListener[] = []; 167 private originDataArray: string[] = []; 168 169 public totalCount(): number { 170 return 0; 171 } 172 173 public getData(index: number): string { 174 return this.originDataArray[index]; 175 } 176 177 // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 178 registerDataChangeListener(listener: DataChangeListener): void { 179 if (this.listeners.indexOf(listener) < 0) { 180 console.info('add listener'); 181 this.listeners.push(listener); 182 } 183 } 184 185 // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听 186 unregisterDataChangeListener(listener: DataChangeListener): void { 187 const pos = this.listeners.indexOf(listener); 188 if (pos >= 0) { 189 console.info('remove listener'); 190 this.listeners.splice(pos, 1); 191 } 192 } 193 194 // 通知LazyForEach组件需要重载所有子组件 195 notifyDataReload(): void { 196 this.listeners.forEach(listener => { 197 listener.onDataReloaded(); 198 }) 199 } 200 201 // 通知LazyForEach组件需要在index对应索引处添加子组件 202 notifyDataAdd(index: number): void { 203 this.listeners.forEach(listener => { 204 listener.onDataAdd(index); 205 }) 206 } 207 208 // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件 209 notifyDataChange(index: number): void { 210 this.listeners.forEach(listener => { 211 listener.onDataChange(index); 212 }) 213 } 214 215 // 通知LazyForEach组件需要在index对应索引处删除该子组件 216 notifyDataDelete(index: number): void { 217 this.listeners.forEach(listener => { 218 listener.onDataDelete(index); 219 }) 220 } 221} 222 223class MyDataSource extends BasicDataSource { 224 private dataArray: string[] = []; 225 226 public totalCount(): number { 227 return this.dataArray.length; 228 } 229 230 public getData(index: number): string { 231 return this.dataArray[index]; 232 } 233 234 public addData(index: number, data: string): void { 235 this.dataArray.splice(index, 0, data); 236 this.notifyDataAdd(index); 237 } 238 239 public pushData(data: string): void { 240 this.dataArray.push(data); 241 this.notifyDataAdd(this.dataArray.length - 1); 242 } 243} 244 245@Entry 246@Component 247struct LforEachTest { 248 private data: MyDataSource = new MyDataSource(); 249 @State @Watch("onMessageUpdated") message: number = 0; 250 251 onMessageUpdated() { 252 console.info(`LazyforEach message callback func ${this.message}`) 253 } 254 255 aboutToAppear() { 256 for (let i = 0; i <= 20; i++) { 257 this.data.pushData(`Hello ${i}`) 258 } 259 } 260 261 build() { 262 Column() { 263 Button('change message').onClick(() => { 264 this.message++ 265 }) 266 List({ space: 3 }) { 267 LazyForEach(this.data, (item: string) => { 268 ListItem() { 269 FreezeChild({ message: this.message, index: item }) 270 } 271 }, (item: string) => item) 272 }.cachedCount(5).height(500) 273 } 274 275 } 276} 277 278@Component({ freezeWhenInactive: true }) 279struct FreezeChild { 280 @Link @Watch("onMessageUpdated") message: number; 281 private index: string = ""; 282 283 aboutToAppear() { 284 console.info(`FreezeChild aboutToAppear index: ${this.index}`) 285 } 286 287 onMessageUpdated() { 288 console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`) 289 } 290 291 build() { 292 Text("message" + `${this.message}, index: ${this.index}`) 293 .width('90%') 294 .height(160) 295 .backgroundColor(0xAFEEEE) 296 .textAlign(TextAlign.Center) 297 .fontSize(30) 298 .fontWeight(FontWeight.Bold) 299 } 300} 301``` 302 303在上面的示例中: 304 3051.点击“change message”更改message的值,当前正在显示的ListItem中的子组件@Watch中注册的方法onMessageUpdated被触发。缓存节点@Watch中注册的方法不会被触发。(如果不加组件冻结,当前正在显示的ListItem和cachcount缓存节点@Watch中注册的方法onMessageUpdated都会触发watch回调。) 306 3072.List区域外的ListItem滑动到List区域内,状态由inactive变为active,对应的@Watch中注册的方法onMessageUpdated被触发。 308 3093.再次点击“change message”更改message的值,仅有当前显示的ListItem中的子组件@Watch中注册的方法onMessageUpdated被触发。 310 311 312 313### Navigation 314 315- 对当前不可见的页面进行冻结,不会触发组件的更新,当返回该页面时,触发@Watch回调进行刷新。 316 317```ts 318@Entry 319@Component 320struct MyNavigationTestStack { 321 @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 322 @State @Watch("info") message: number = 0; 323 @State logNumber: number = 0; 324 325 info() { 326 console.info(`freeze-test MyNavigation message callback ${this.message}`); 327 } 328 329 @Builder 330 PageMap(name: string) { 331 if (name === 'pageOne') { 332 pageOneStack({ message: this.message, logNumber: this.logNumber }) 333 } else if (name === 'pageTwo') { 334 pageTwoStack({ message: this.message, logNumber: this.logNumber }) 335 } else if (name === 'pageThree') { 336 pageThreeStack({ message: this.message, logNumber: this.logNumber }) 337 } 338 } 339 340 build() { 341 Column() { 342 Button('change message') 343 .onClick(() => { 344 this.message++; 345 }) 346 Navigation(this.pageInfo) { 347 Column() { 348 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 349 .width('80%') 350 .height(40) 351 .margin(20) 352 .onClick(() => { 353 this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈 354 }) 355 } 356 }.title('NavIndex') 357 .navDestination(this.PageMap) 358 .mode(NavigationMode.Stack) 359 } 360 } 361} 362 363@Component 364struct pageOneStack { 365 @Consume('pageInfo') pageInfo: NavPathStack; 366 @State index: number = 1; 367 @Link message: number; 368 @Link logNumber: number; 369 370 build() { 371 NavDestination() { 372 Column() { 373 NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber }) 374 Text("cur stack size:" + `${this.pageInfo.size()}`) 375 .fontSize(30) 376 .fontWeight(FontWeight.Bold) 377 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 378 .width('80%') 379 .height(40) 380 .margin(20) 381 .onClick(() => { 382 this.pageInfo.pushPathByName('pageTwo', null); 383 }) 384 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 385 .width('80%') 386 .height(40) 387 .margin(20) 388 .onClick(() => { 389 this.pageInfo.pop(); 390 }) 391 }.width('100%').height('100%') 392 }.title('pageOne') 393 .onBackPressed(() => { 394 this.pageInfo.pop(); 395 return true; 396 }) 397 } 398} 399 400@Component 401struct pageTwoStack { 402 @Consume('pageInfo') pageInfo: NavPathStack; 403 @State index: number = 2; 404 @Link message: number; 405 @Link logNumber: number; 406 407 build() { 408 NavDestination() { 409 Column() { 410 NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber }) 411 Text("cur stack size:" + `${this.pageInfo.size()}`) 412 .fontSize(30) 413 .fontWeight(FontWeight.Bold) 414 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 415 .width('80%') 416 .height(40) 417 .margin(20) 418 .onClick(() => { 419 this.pageInfo.pushPathByName('pageThree', null); 420 }) 421 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 422 .width('80%') 423 .height(40) 424 .margin(20) 425 .onClick(() => { 426 this.pageInfo.pop(); 427 }) 428 }.width('100%').height('100%') 429 }.title('pageTwo') 430 .onBackPressed(() => { 431 this.pageInfo.pop(); 432 return true; 433 }) 434 } 435} 436 437@Component 438struct pageThreeStack { 439 @Consume('pageInfo') pageInfo: NavPathStack; 440 @State index: number = 3; 441 @Link message: number; 442 @Link logNumber: number; 443 444 build() { 445 NavDestination() { 446 Column() { 447 NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber }) 448 Text("cur stack size:" + `${this.pageInfo.size()}`) 449 .fontSize(30) 450 .fontWeight(FontWeight.Bold) 451 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 452 .width('80%') 453 .height(40) 454 .margin(20) 455 .onClick(() => { 456 this.pageInfo.pushPathByName('pageOne', null); 457 }) 458 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 459 .width('80%') 460 .height(40) 461 .margin(20) 462 .onClick(() => { 463 this.pageInfo.pop(); 464 }) 465 }.width('100%').height('100%') 466 }.title('pageThree') 467 .onBackPressed(() => { 468 this.pageInfo.pop(); 469 return true; 470 }) 471 } 472} 473 474@Component({ freezeWhenInactive: true }) 475struct NavigationContentMsgStack { 476 @Link @Watch("info") message: number; 477 @Link index: number; 478 @Link logNumber: number; 479 480 info() { 481 console.info(`freeze-test NavigationContent message callback ${this.message}`); 482 console.info(`freeze-test ---- called by content ${this.index}`); 483 this.logNumber++; 484 } 485 486 build() { 487 Column() { 488 Text("msg:" + `${this.message}`) 489 .fontSize(30) 490 .fontWeight(FontWeight.Bold) 491 Text("log number:" + `${this.logNumber}`) 492 .fontSize(30) 493 .fontWeight(FontWeight.Bold) 494 } 495 } 496} 497``` 498 499在上面的示例中: 500 5011.点击“change message”更改message的值,当前正在显示的MyNavigationTestStack组件中的@Watch中注册的方法info被触发。 502 5032.点击“Next Page”切换到PageOne,创建pageOneStack节点。 504 5053.再次点击“change message”更改message的值,仅pageOneStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。 506 5074.再次点击“Next Page”切换到PageTwo,创建pageTwoStack节点。 508 5095.再次点击“change message”更改message的值,仅pageTwoStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。 510 5116.再次点击“Next Page”切换到PageThree,创建pageThreeStack节点。 512 5137.再次点击“change message”更改message的值,仅pageThreeStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。 514 5158.点击“Back Page”回到PageTwo,此时,仅pageTwoStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。 516 5179.再次点击“Back Page”回到PageOne,此时,仅pageOneStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。 518 51910.再次点击“Back Page”回到初始页,此时,无任何触发。 520 521