• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![TabContent.gif](figures/TabContent.gif)
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![FrezzeLazyforEach.gif](figures/FrezzeLazyforEach.gif)
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![navigation-freeze.gif](figures/navigation-freeze.gif)