ToB企服应用市场:ToB评测及商务社交产业平台
标题:
(五)鸿蒙HarmonyOS主力开发语言ArkTS-数据懒加载(LazyForEach)
[打印本页]
作者:
渣渣兔
时间:
2024-10-26 01:54
标题:
(五)鸿蒙HarmonyOS主力开发语言ArkTS-数据懒加载(LazyForEach)
系列文章目录
(一)鸿蒙HarmonyOS开发底子
(二)鸿蒙HarmonyOS主力开发语言ArkTS-根本语法
(三)鸿蒙HarmonyOS主力开发语言ArkTS-状态管理
(四)鸿蒙HarmonyOS主力开发语言ArkTS-渲染控制
LazyForEach:数据懒加载
LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视地区按需创建组件,当组件滑出可视地区外时,框架会举行组件销毁回收以降低内存占用。
接口描述
1. LazyForEach(
2. dataSource: IDataSource, // 需要进行数据迭代的数据源
3. itemGenerator: (item: any, index: number) => void, // 子组件生成函数
4. keyGenerator?: (item: any, index: number) => string // 键值生成函数
5. ): void
复制代码
参数:
参数名
参数类型
必填
参数描述
dataSource
IDataSource
是
LazyForEach数据源,需要开发者实现相干接口。
itemGenerator
(item: any, index:number) => void
是
子组件天生函数,为数组中的每一个数据项创建一个子组件。
说明:
item是当前数据项,index是数据项索引值。
itemGenerator的函数体必须使用大括号{...}。itemGenerator每次迭代只能并且必须天生一个子组件。itemGenerator中可以使用if语句,但是必须保证if语句每个分支都会创建一个相同类型的子组件。itemGenerator中不答应使用ForEach和LazyForEach语句。
keyGenerator
(item: any, index:number) => string
否
键值天生函数,用于给数据源中的每一个数据项天生唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项更换时,被更换项的键值和新项的键值必须不同。键值天生器的功能是可选的,但是,为了使开发框架能够更好地辨认数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值天生器,则LazyForEach中的所有节点都将重建。
说明:
item是当前数据项,index是数据项索引值。
数据源中的每一个数据项天生的键值不能重复。
IDataSource类型说明
1. interface IDataSource {
2. totalCount(): number; // 获得数据总数
3. getData(index: number): Object; // 获取索引值对应的数据
4. registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器
5. unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
6. }
复制代码
接口声明
参数类型
说明
totalCount(): number
-
获得数据总数。
getData(index: number): any
number
获取索引值index对应的数据。
index:获取数据对应的索引值。
registerDataChangeListener(listener
ataChangeListener): void
DataChangeListener
注册数据改变的监听器。
listener:数据变化监听器
unregisterDataChangeListener(listener
ataChangeListener): void
DataChangeListener
注销数据改变的监听器。
listener:数据变化监听器
DataChangeListener类型说明
1. interface DataChangeListener {
2. onDataReloaded(): void; // 重新加载数据完成后调用
3. onDataAdded(index: number): void; // 添加数据完成后调用
4. onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
5. onDataDeleted(index: number): void; // 删除数据完成后调用
6. onDataChanged(index: number): void; // 改变数据完成后调用
7. onDataAdd(index: number): void; // 添加数据完成后调用
8. onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
9. onDataDelete(index: number): void; // 删除数据完成后调用
10. onDataChange(index: number): void; // 改变数据完成后调用
11. }
复制代码
接口声明
参数类型
说明
onDataReloaded(): void
-
通知组件重新加载所有数据。
键值没有变化的数据项会使用原先的子组件,键值发生变化的会重建子组件。
onDataAdd(index: number): void8+
number
通知组件index的位置有数据添加。
index:数据添加位置的索引值。
onDataMove(from: number, to: number): void8+
from: number,
to: number
通知组件数据有移动。
from: 数据移动起始位置,to: 数据移动目标位置。
说明:
数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。
onDataDelete(index: number):void8+
number
通知组件删除index位置的数据并革新LazyForEach的展示内容。
index:数据删除位置的索引值。
说明:
需要保证dataSource中的对应数据已经在调用onDataDelete前删除,否则页面渲染将出现未定义的行为。
onDataChange(index: number): void8+
number
通知组件index的位置有数据有变化。
index:数据变化位置的索引值。
onDataAdded(index: number):void(deprecated)
number
通知组件index的位置有数据添加。
从API 8开始,建议使用onDataAdd。
index:数据添加位置的索引值。
onDataMoved(from: number, to: number): void(deprecated)
from: number,
to: number
通知组件数据有移动。
从API 8开始,建议使用onDataMove。
from: 数据移动起始位置,to: 数据移动目标位置。
将from和to位置的数据举行互换。
说明:
数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。
onDataDeleted(index: number):void(deprecated)
number
通知组件删除index位置的数据并革新LazyForEach的展示内容。
从API 8开始,建议使用onDataDelete。
index:数据删除位置的索引值。
onDataChanged(index: number): void(deprecated)
number
通知组件index的位置有数据有变化。
从API 8开始,建议使用onDataChange。
index:数据变化监听器。
使用限定
LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
LazyForEach在每次迭代中,必须创建且只答应创建一个子组件。
天生的子组件必须是答应包罗在LazyForEach父容器组件中的子组件。
答应LazyForEach包罗在if/else条件渲染语句中,也答应LazyForEach中出现if/else条件渲染语句。
键值天生器必须针对每个数据天生唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。
LazyForEach必须使用DataChangeListener对象来举行更新,第一个参数dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI革新。
为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要天生不同于原来的键值来触发组件革新。
键值天生规则
在LazyForEach循环渲染过程中,系统会为每个item天生一个唯一且长期的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被更换或修改,并会基于新的键值创建一个新的组件。
LazyForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的天生规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值天生函数,即(item: any, index: number) => { return viewId + ‘-’ + index.toString(); }, viewId在编译器转换过程中天生,同一个LazyForEach组件内其viewId是一致的。
组件创建规则
在确定键值天生规则后,LazyForEach的第二个参数itemGenerator函数会根据键值天生规则为数据源的每个数组项创建组件。组件的创建包括两种情况:LazyForEach首次渲染和LazyForEach非首次渲染。
首次渲染
天生不同键值
在LazyForEach首次渲染时,会根据上述键值天生规则为数据源的每个数组项天生唯一键值,并创建相应的组件。
1. // Basic implementation of IDataSource to handle data listener
2. class BasicDataSource implements IDataSource {
3. private listeners: DataChangeListener[] = [];
4. private originDataArray: string[] = [];
6. public totalCount(): number {
7. return 0;
8. }
10. public getData(index: number): string {
11. return this.originDataArray[index];
12. }
14. // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
15. registerDataChangeListener(listener: DataChangeListener): void {
16. if (this.listeners.indexOf(listener) < 0) {
17. console.info('add listener');
18. this.listeners.push(listener);
19. }
20. }
22. // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
23. unregisterDataChangeListener(listener: DataChangeListener): void {
24. const pos = this.listeners.indexOf(listener);
25. if (pos >= 0) {
26. console.info('remove listener');
27. this.listeners.splice(pos, 1);
28. }
29. }
31. // 通知LazyForEach组件需要重载所有子组件
32. notifyDataReload(): void {
33. this.listeners.forEach(listener => {
34. listener.onDataReloaded();
35. })
36. }
38. // 通知LazyForEach组件需要在index对应索引处添加子组件
39. notifyDataAdd(index: number): void {
40. this.listeners.forEach(listener => {
41. listener.onDataAdd(index);
42. })
43. }
45. // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
46. notifyDataChange(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataChange(index);
49. })
50. }
52. // 通知LazyForEach组件需要在index对应索引处删除该子组件
53. notifyDataDelete(index: number): void {
54. this.listeners.forEach(listener => {
55. listener.onDataDelete(index);
56. })
57. }
58. }
60. class MyDataSource extends BasicDataSource {
61. private dataArray: string[] = [];
63. public totalCount(): number {
64. return this.dataArray.length;
65. }
67. public getData(index: number): string {
68. return this.dataArray[index];
69. }
71. public addData(index: number, data: string): void {
72. this.dataArray.splice(index, 0, data);
73. this.notifyDataAdd(index);
74. }
76. public pushData(data: string): void {
77. this.dataArray.push(data);
78. this.notifyDataAdd(this.dataArray.length - 1);
79. }
80. }
82. @Entry
83. @Component
84. struct MyComponent {
85. private data: MyDataSource = new MyDataSource();
87. aboutToAppear() {
88. for (let i = 0; i <= 20; i++) {
89. this.data.pushData(`Hello ${i}`)
90. }
91. }
93. build() {
94. List({ space: 3 }) {
95. LazyForEach(this.data, (item: string) => {
96. ListItem() {
97. Row() {
98. Text(item).fontSize(50)
99. .onAppear(() => {
100. console.info("appear:" + item)
101. })
102. }.margin({ left: 10, right: 10 })
103. }
104. }, (item: string) => item)
105. }.cachedCount(5)
106. }
107. }
复制代码
在上述代码中,键值天生规则是keyGenerator函数的返回值item。在LazyForEach循环渲染时,其为数据源数组项依次天生键值Hello 0、Hello 1 … Hello 20,并创建对应的ListItem子组件渲染到界面上。
运行效果如下图所示。
图1
LazyForEach正常首次渲染
键值相同时错误渲染
当不同数据项天生的键值相同时,框架的行为是不可猜测的。例如,在以下代码中,LazyForEach渲染的数据项键值均相同,在滑动过程中,LazyForEach会对划入划出当前页面的子组件举行预加载,而新建的子组件和销毁的原子组件具有相同的键值,框架大概存在取用缓存错误的情况,导致子组件渲染有问题。
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: string[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): string {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: string[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): string {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: string): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: string): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
73. }
75. @Entry
76. @Component
77. struct MyComponent {
78. private data: MyDataSource = new MyDataSource();
80. aboutToAppear() {
81. for (let i = 0; i <= 20; i++) {
82. this.data.pushData(`Hello ${i}`)
83. }
84. }
86. build() {
87. List({ space: 3 }) {
88. LazyForEach(this.data, (item: string) => {
89. ListItem() {
90. Row() {
91. Text(item).fontSize(50)
92. .onAppear(() => {
93. console.info("appear:" + item)
94. })
95. }.margin({ left: 10, right: 10 })
96. }
97. }, (item: string) => 'same key')
98. }.cachedCount(5)
99. }
100. }
复制代码
运行效果如下图所示。可以看到Hello 0在滑动过程中被错误渲染为Hello 13。
图2
LazyForEach存在相同键值
非首次渲染
当LazyForEach数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用listener对应的接口,通知LazyForEach做相应的更新,各使用场景如下。
添加数据
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: string[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): string {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: string[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): string {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: string): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: string): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
73. }
75. @Entry
76. @Component
77. struct MyComponent {
78. private data: MyDataSource = new MyDataSource();
80. aboutToAppear() {
81. for (let i = 0; i <= 20; i++) {
82. this.data.pushData(`Hello ${i}`)
83. }
84. }
86. build() {
87. List({ space: 3 }) {
88. LazyForEach(this.data, (item: string) => {
89. ListItem() {
90. Row() {
91. Text(item).fontSize(50)
92. .onAppear(() => {
93. console.info("appear:" + item)
94. })
95. }.margin({ left: 10, right: 10 })
96. }
97. .onClick(() => {
98. // 点击追加子组件
99. this.data.pushData(`Hello ${this.data.totalCount()}`);
100. })
101. }, (item: string) => item)
102. }.cachedCount(5)
103. }
104. }
复制代码
当我们点击LazyForEach的子组件时,首先调用数据源data的pushData方法,该方法会在数据源末尾添加数据并调用notifyDataAdd方法。在notifyDataAdd方法内会又调用listener.onDataAdd方法,该方法会通知LazyForEach在该处有数据添加,LazyForEach便会在该索引处新建子组件。
运行效果如下图所示。
图3
LazyForEach添加数据
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: string[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): string {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. dataArray: string[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): string {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: string): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: string): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
74. public deleteData(index: number): void {
75. this.dataArray.splice(index, 1);
76. this.notifyDataDelete(index);
77. }
78. }
80. @Entry
81. @Component
82. struct MyComponent {
83. private data: MyDataSource = new MyDataSource();
85. aboutToAppear() {
86. for (let i = 0; i <= 20; i++) {
87. this.data.pushData(`Hello ${i}`)
88. }
89. }
91. build() {
92. List({ space: 3 }) {
93. LazyForEach(this.data, (item: string, index: number) => {
94. ListItem() {
95. Row() {
96. Text(item).fontSize(50)
97. .onAppear(() => {
98. console.info("appear:" + item)
99. })
100. }.margin({ left: 10, right: 10 })
101. }
102. .onClick(() => {
103. // 点击删除子组件
104. this.data.deleteData(this.data.dataArray.indexOf(item));
105. })
106. }, (item: string) => item)
107. }.cachedCount(5)
108. }
109. }
复制代码
当我们点击LazyForEach的子组件时,首先调用数据源data的deleteData方法,该方法会删除数据源对应索引处的数据并调用notifyDataDelete方法。在notifyDataDelete方法内会又调用listener.onDataDelete方法,该方法会通知LazyForEach在该处有数据删除,LazyForEach便会在该索引处删除对应子组件。
运行效果如下图所示。
图4
LazyForEach删除数据
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: string[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): string {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: string[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): string {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: string): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: string): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
74. public deleteData(index: number): void {
75. this.dataArray.splice(index, 1);
76. this.notifyDataDelete(index);
77. }
79. public changeData(index: number, data: string): void {
80. this.dataArray.splice(index, 1, data);
81. this.notifyDataChange(index);
82. }
83. }
85. @Entry
86. @Component
87. struct MyComponent {
88. private moved: number[] = [];
89. private data: MyDataSource = new MyDataSource();
91. aboutToAppear() {
92. for (let i = 0; i <= 20; i++) {
93. this.data.pushData(`Hello ${i}`)
94. }
95. }
98. build() {
99. List({ space: 3 }) {
100. LazyForEach(this.data, (item: string, index: number) => {
101. ListItem() {
102. Row() {
103. Text(item).fontSize(50)
104. .onAppear(() => {
105. console.info("appear:" + item)
106. })
107. }.margin({ left: 10, right: 10 })
108. }
109. .onClick(() => {
110. this.data.changeData(index, item + '00');
111. })
112. }, (item: string) => item)
113. }.cachedCount(5)
114. }
115. }
复制代码
当我们点击LazyForEach的子组件时,首先改变当前数据,然后调用数据源data的changeData方法,在该方法内会调用notifyDataChange方法。在notifyDataChange方法内会又调用listener.onDataChange方法,该方法通知LazyForEach组件该处有数据发生变化,LazyForEach便会在对应索引处重建子组件。
运行效果如下图所示。
图5
LazyForEach改变单个数据
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: string[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): string {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: string[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): string {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: string): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: string): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
74. public deleteData(index: number): void {
75. this.dataArray.splice(index, 1);
76. this.notifyDataDelete(index);
77. }
79. public changeData(index: number): void {
80. this.notifyDataChange(index);
81. }
83. public reloadData(): void {
84. this.notifyDataReload();
85. }
87. public modifyAllData(): void {
88. this.dataArray = this.dataArray.map((item: string) => {
89. return item + '0';
90. })
91. }
92. }
94. @Entry
95. @Component
96. struct MyComponent {
97. private moved: number[] = [];
98. private data: MyDataSource = new MyDataSource();
100. aboutToAppear() {
101. for (let i = 0; i <= 20; i++) {
102. this.data.pushData(`Hello ${i}`)
103. }
104. }
106. build() {
107. List({ space: 3 }) {
108. LazyForEach(this.data, (item: string, index: number) => {
109. ListItem() {
110. Row() {
111. Text(item).fontSize(50)
112. .onAppear(() => {
113. console.info("appear:" + item)
114. })
115. }.margin({ left: 10, right: 10 })
116. }
117. .onClick(() => {
118. this.data.modifyAllData();
119. this.data.reloadData();
120. })
121. }, (item: string) => item)
122. }.cachedCount(5)
123. }
124. }
复制代码
当我们点击LazyForEach的子组件时,首先调用data的modifyAllData方法改变了数据源中的所有数据,然后调用数据源的reloadData方法,在该方法内会调用notifyDataReload方法。在notifyDataReload方法内会又调用listener.onDataReloaded方法,通知LazyForEach需要重建所有子节点。LazyForEach会将原所有数据项和新所有数据项一一做键值比对,如有相同键值则使用缓存,若键值不同则重新构建。
运行效果如下图所示。
图6
LazyForEach改变多个数据
若仅靠LazyForEach的革新机制,当item变化时若想更新子组件,需要将原来的子组件全部销毁再重新构建,在子组件布局较为复杂的情况下,靠改变键值去革新渲染性能较低。因此框架提供了@Observed与@ObjectLink机制举行深度观测,可以做到仅革新使用了该属性的组件,提高渲染性能。开发者可根据其自身业务特点选择使用哪种革新方式
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: StringData[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): StringData {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: StringData[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): StringData {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: StringData): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: StringData): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
73. }
75. @Observed
76. class StringData {
77. message: string;
78. constructor(message: string) {
79. this.message = message;
80. }
81. }
83. @Entry
84. @Component
85. struct MyComponent {
86. private moved: number[] = [];
87. @State data: MyDataSource = new MyDataSource();
89. aboutToAppear() {
90. for (let i = 0; i <= 20; i++) {
91. this.data.pushData(new StringData(`Hello ${i}`));
92. }
93. }
95. build() {
96. List({ space: 3 }) {
97. LazyForEach(this.data, (item: StringData, index: number) => {
98. ListItem() {
99. ChildComponent({data: item})
100. }
101. .onClick(() => {
102. item.message += '0';
103. })
104. }, (item: StringData, index: number) => index.toString())
105. }.cachedCount(5)
106. }
107. }
109. @Component
110. struct ChildComponent {
111. @ObjectLink data: StringData
112. build() {
113. Row() {
114. Text(this.data.message).fontSize(50)
115. .onAppear(() => {
116. console.info("appear:" + this.data.message)
117. })
118. }.margin({ left: 10, right: 10 })
119. }
120. }
复制代码
此时点击LazyForEach子组件改变item.message时,重渲染依赖的是ChildComponent的@ObjectLink成员变量对其子属性的监听,此时框架只会革新Text(this.data.message),不会去重建整个ListItem子组件。
图7
LazyForEach改变数据子属性
常见使用问题
渲染效果非预期
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: string[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): string {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: string[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): string {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: string): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: string): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
74. public deleteData(index: number): void {
75. this.dataArray.splice(index, 1);
76. this.notifyDataDelete(index);
77. }
78. }
80. @Entry
81. @Component
82. struct MyComponent {
83. private data: MyDataSource = new MyDataSource();
85. aboutToAppear() {
86. for (let i = 0; i <= 20; i++) {
87. this.data.pushData(`Hello ${i}`)
88. }
89. }
91. build() {
92. List({ space: 3 }) {
93. LazyForEach(this.data, (item: string, index: number) => {
94. ListItem() {
95. Row() {
96. Text(item).fontSize(50)
97. .onAppear(() => {
98. console.info("appear:" + item)
99. })
100. }.margin({ left: 10, right: 10 })
101. }
102. .onClick(() => {
103. // 点击删除子组件
104. this.data.deleteData(index);
105. })
106. }, (item: string) => item)
107. }.cachedCount(5)
108. }
109. }
复制代码
图8
LazyForEach删除数据非预期
当我们多次点击子组件时,会发现删除的并不肯定是我们点击的那个子组件。缘故原由是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其index均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的index,其itemGenerator中的index并没有发生变化,所以删除效果和预期不符。
修复代码如下所示。
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: string[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): string {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: string[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): string {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: string): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: string): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
74. public deleteData(index: number): void {
75. this.dataArray.splice(index, 1);
76. this.notifyDataDelete(index);
77. }
79. public reloadData(): void {
80. this.notifyDataReload();
81. }
82. }
84. @Entry
85. @Component
86. struct MyComponent {
87. private data: MyDataSource = new MyDataSource();
89. aboutToAppear() {
90. for (let i = 0; i <= 20; i++) {
91. this.data.pushData(`Hello ${i}`)
92. }
93. }
95. build() {
96. List({ space: 3 }) {
97. LazyForEach(this.data, (item: string, index: number) => {
98. ListItem() {
99. Row() {
100. Text(item).fontSize(50)
101. .onAppear(() => {
102. console.info("appear:" + item)
103. })
104. }.margin({ left: 10, right: 10 })
105. }
106. .onClick(() => {
107. // 点击删除子组件
108. this.data.deleteData(index);
109. // 重置所有子组件的index索引
110. this.data.reloadData();
111. })
112. }, (item: string, index: number) => item + index.toString())
113. }.cachedCount(5)
114. }
115. }
复制代码
在删除一个数据项后调用reloadData方法,重建反面的数据项,以达到更新index索引的目的。
图9
修复LazyForEach删除数据非预期
重渲染时图片闪烁
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: StringData[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): StringData {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: StringData[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): StringData {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: StringData): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: StringData): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
74. public reloadData(): void {
75. this.notifyDataReload();
76. }
77. }
79. class StringData {
80. message: string;
81. imgSrc: Resource;
82. constructor(message: string, imgSrc: Resource) {
83. this.message = message;
84. this.imgSrc = imgSrc;
85. }
86. }
88. @Entry
89. @Component
90. struct MyComponent {
91. private moved: number[] = [];
92. private data: MyDataSource = new MyDataSource();
94. aboutToAppear() {
95. for (let i = 0; i <= 20; i++) {
96. this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
97. }
98. }
100. build() {
101. List({ space: 3 }) {
102. LazyForEach(this.data, (item: StringData, index: number) => {
103. ListItem() {
104. Column() {
105. Text(item.message).fontSize(50)
106. .onAppear(() => {
107. console.info("appear:" + item.message)
108. })
109. Image(item.imgSrc)
110. .width(500)
111. .height(200)
112. }.margin({ left: 10, right: 10 })
113. }
114. .onClick(() => {
115. item.message += '00';
116. this.data.reloadData();
117. })
118. }, (item: StringData, index: number) => JSON.stringify(item))
119. }.cachedCount(5)
120. }
121. }
复制代码
图10
LazyForEach仅改变笔墨但是图片闪烁问题
在我们点击ListItem子组件时,我们只改变了数据项的message属性,但是LazyForEach的革新机制会导致整个ListItem被重建。由于Image组件是异步革新,所以视觉上图片会发生闪烁。为了办理这种情况我们应该使用@ObjectLink和@Observed去单独革新使用了item.message的Text组件。
修复代码如下所示。
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: StringData[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): StringData {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: StringData[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): StringData {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: StringData): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: StringData): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
73. }
75. @Observed
76. class StringData {
77. message: string;
78. imgSrc: Resource;
79. constructor(message: string, imgSrc: Resource) {
80. this.message = message;
81. this.imgSrc = imgSrc;
82. }
83. }
85. @Entry
86. @Component
87. struct MyComponent {
88. @State data: MyDataSource = new MyDataSource();
90. aboutToAppear() {
91. for (let i = 0; i <= 20; i++) {
92. this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
93. }
94. }
96. build() {
97. List({ space: 3 }) {
98. LazyForEach(this.data, (item: StringData, index: number) => {
99. ListItem() {
100. ChildComponent({data: item})
101. }
102. .onClick(() => {
103. item.message += '0';
104. })
105. }, (item: StringData, index: number) => index.toString())
106. }.cachedCount(5)
107. }
108. }
110. @Component
111. struct ChildComponent {
112. @ObjectLink data: StringData
113. build() {
114. Column() {
115. Text(this.data.message).fontSize(50)
116. .onAppear(() => {
117. console.info("appear:" + this.data.message)
118. })
119. Image(this.data.imgSrc)
120. .width(500)
121. .height(200)
122. }.margin({ left: 10, right: 10 })
123. }
124. }
复制代码
图11
修复LazyForEach仅改变笔墨但是图片闪烁问题
@ObjectLink属性变化UI未更新
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: StringData[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): StringData {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: StringData[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): StringData {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: StringData): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: StringData): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
73. }
75. @Observed
76. class StringData {
77. message: NestedString;
78. constructor(message: NestedString) {
79. this.message = message;
80. }
81. }
83. @Observed
84. class NestedString {
85. message: string;
86. constructor(message: string) {
87. this.message = message;
88. }
89. }
91. @Entry
92. @Component
93. struct MyComponent {
94. private moved: number[] = [];
95. @State data: MyDataSource = new MyDataSource();
97. aboutToAppear() {
98. for (let i = 0; i <= 20; i++) {
99. this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
100. }
101. }
103. build() {
104. List({ space: 3 }) {
105. LazyForEach(this.data, (item: StringData, index: number) => {
106. ListItem() {
107. ChildComponent({data: item})
108. }
109. .onClick(() => {
110. item.message.message += '0';
111. })
112. }, (item: StringData, index: number) => item.toString() + index.toString())
113. }.cachedCount(5)
114. }
115. }
117. @Component
118. struct ChildComponent {
119. @ObjectLink data: StringData
120. build() {
121. Row() {
122. Text(this.data.message.message).fontSize(50)
123. .onAppear(() => {
124. console.info("appear:" + this.data.message.message)
125. })
126. }.margin({ left: 10, right: 10 })
127. }
128. }
复制代码
图12
ObjectLink属性变化后UI未更新
@ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体请检察@ObjectLink与@Observed的具体使用方法和限定条件。
修复代码如下所示。
1. class BasicDataSource implements IDataSource {
2. private listeners: DataChangeListener[] = [];
3. private originDataArray: StringData[] = [];
5. public totalCount(): number {
6. return 0;
7. }
9. public getData(index: number): StringData {
10. return this.originDataArray[index];
11. }
13. registerDataChangeListener(listener: DataChangeListener): void {
14. if (this.listeners.indexOf(listener) < 0) {
15. console.info('add listener');
16. this.listeners.push(listener);
17. }
18. }
20. unregisterDataChangeListener(listener: DataChangeListener): void {
21. const pos = this.listeners.indexOf(listener);
22. if (pos >= 0) {
23. console.info('remove listener');
24. this.listeners.splice(pos, 1);
25. }
26. }
28. notifyDataReload(): void {
29. this.listeners.forEach(listener => {
30. listener.onDataReloaded();
31. })
32. }
34. notifyDataAdd(index: number): void {
35. this.listeners.forEach(listener => {
36. listener.onDataAdd(index);
37. })
38. }
40. notifyDataChange(index: number): void {
41. this.listeners.forEach(listener => {
42. listener.onDataChange(index);
43. })
44. }
46. notifyDataDelete(index: number): void {
47. this.listeners.forEach(listener => {
48. listener.onDataDelete(index);
49. })
50. }
51. }
53. class MyDataSource extends BasicDataSource {
54. private dataArray: StringData[] = [];
56. public totalCount(): number {
57. return this.dataArray.length;
58. }
60. public getData(index: number): StringData {
61. return this.dataArray[index];
62. }
64. public addData(index: number, data: StringData): void {
65. this.dataArray.splice(index, 0, data);
66. this.notifyDataAdd(index);
67. }
69. public pushData(data: StringData): void {
70. this.dataArray.push(data);
71. this.notifyDataAdd(this.dataArray.length - 1);
72. }
73. }
75. @Observed
76. class StringData {
77. message: NestedString;
78. constructor(message: NestedString) {
79. this.message = message;
80. }
81. }
83. @Observed
84. class NestedString {
85. message: string;
86. constructor(message: string) {
87. this.message = message;
88. }
89. }
91. @Entry
92. @Component
93. struct MyComponent {
94. private moved: number[] = [];
95. @State data: MyDataSource = new MyDataSource();
97. aboutToAppear() {
98. for (let i = 0; i <= 20; i++) {
99. this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
100. }
101. }
103. build() {
104. List({ space: 3 }) {
105. LazyForEach(this.data, (item: StringData, index: number) => {
106. ListItem() {
107. ChildComponent({data: item})
108. }
109. .onClick(() => {
110. item.message = new NestedString(item.message.message + '0');
111. })
112. }, (item: StringData, index: number) => item.toString() + index.toString())
113. }.cachedCount(5)
114. }
115. }
117. @Component
118. struct ChildComponent {
119. @ObjectLink data: StringData
120. build() {
121. Row() {
122. Text(this.data.message.message).fontSize(50)
123. .onAppear(() => {
124. console.info("appear:" + this.data.message.message)
125. })
126. }.margin({ left: 10, right: 10 })
127. }
128. }
复制代码
图13
修复ObjectLink属性变化后UI更新
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4