• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Freezing a Custom Component
2
3When a custom component is inactive, it can be frozen so that its state variable does not respond to updates. That is, the @Watch decorated method is not called, and the node associated with the state variable is not re-rendered. You can use the **freezeWhenInactive** attribute to specify whether to freeze a custom component. If no parameter is passed in, the feature is disabled. This feature works in following scenarios: page routing, **\<TabContent>**, and **LazyforEach**.
4
5
6> **NOTE**
7>
8> Custom component freezing is supported since API version 11.
9
10## Use Scenarios
11
12### Page Routing
13
14- When page A calls the **router.pushUrl** API to jump to page B, page A is hidden and invisible. In this case, if the state variable on page A is updated, page A is not re-rendered.
15
16- The freezing feature does not work when the application is running in the background.
17
18Page 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
48Page 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
81In the preceding example:
82
831. When the button **first page storLink + 1** on page A is clicked, the **storLink** state variable is updated, and the @Watch decorated **first** method is called.
84
852. Through **router.pushUrl({url:'pages/second'})**, page B is displayed, and page A is hidden with its state changing from active to inactive.
86
873. When the button **this.storLink2 += 2** on page B is clicked, only the @Watch decorated **second** method of page B is called, because page A has been frozen when inactive.
88
894. When the **back** button is clicked, page B is destroyed, and page A changes from inactive to active. At this time, if the state variable of page A is updated, the @Watch decorated **first** method of page A is called again.
90
91
92### \<TabContent>
93
94- You can freeze invisible **\<TabContent>** components in the **\<Tabs>** container so that they do not trigger UI re-rendering.
95
96- During initial rendering, only the **\<TabContent>** component that is being displayed is created. All **\<TabContent>** components are created only after all of them have been switched to.
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          }
119
120          TabContent() {
121            FreezeChild({ message: this.message })
122          }
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
148In the preceding example:
149
1501. When **change message** is clicked, the value of **message** changes, and the @Watch decorated **onMessageUpdated** method of the **\<TabContent>** component being displayed is called.
151
1522. When you switch to another **\<TabContent>** component, it switches from inactive to active, and the corresponding @Watch decorated **onMessageUpdated** method is called.
153
1543. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **onMessageUpdated** method of the **\<TabContent>** component being displayed is called.
155
156![TabContent.gif](figures/TabContent.gif)
157
158
159### LazyforEach
160
161- You can freeze custom components cached in **LazyforEach** so that they do not trigger UI re-rendering.
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  // This method is called by the framework to add a listener to the LazyForEach data source.
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  // This method is called by the framework to remove the listener from the LazyForEach data source.
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  // Notify LazyForEach that all child components need to be reloaded.
195  notifyDataReload(): void {
196    this.listeners.forEach(listener => {
197      listener.onDataReloaded();
198    })
199  }
200
201  // Notify LazyForEach that a child component needs to be added for the data item with the specified index.
202  notifyDataAdd(index: number): void {
203    this.listeners.forEach(listener => {
204      listener.onDataAdd(index);
205    })
206  }
207
208  // Notify LazyForEach that the data item with the specified index has changed and the child component needs to be rebuilt.
209  notifyDataChange(index: number): void {
210    this.listeners.forEach(listener => {
211      listener.onDataChange(index);
212    })
213  }
214
215  // Notify LazyForEach that the child component that matches the specified index needs to be deleted.
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,
270              index: item })
271          }
272        }, (item: string) => item)
273      }.cachedCount(5).height(500)
274    }
275
276  }
277}
278
279@Component({ freezeWhenInactive: true })
280struct FreezeChild {
281  @Link @Watch("onMessageUpdated") message: number;
282  private index: string = "";
283
284  aboutToAppear() {
285    console.info(`FreezeChild aboutToAppear index: ${this.index}`)
286  }
287
288  onMessageUpdated() {
289    console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`)
290  }
291
292  build() {
293    Text("message" + `${this.message}, index: ${this.index}`)
294      .width('90%')
295      .height(160)
296      .backgroundColor(0xAFEEEE)
297      .textAlign(TextAlign.Center)
298      .fontSize(30)
299      .fontWeight(FontWeight.Bold)
300  }
301}
302```
303
304In the preceding example:
305
3061. When **change message** is clicked, the value of **message** changes, the @Watch decorated **onMessageUpdated** method of the list items being displayed is called, and that of the cached list items is not called. (If the component is not frozen, the @Watch decorated **onMessageUpdated** method of both list items that are being displayed and cached list items is called.)
307
3082. When a list item moves from outside the list content area into the list content area, it switches from inactive to active, and the corresponding @Watch decorated **onMessageUpdated** method is called.
309
3103. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **onMessageUpdated** method of the list items being displayed is called.
311
312![FrezzeLazyforEach.gif](figures/FrezzeLazyforEach.gif)
313