构建第一个ArkTS应用之@LazyForEach:数据懒加载

LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。

接口描述

LazyForEach(dataSource: IDataSource,             // 需要进行数据迭代的数据源itemGenerator: (item: any, index: number) => void,  // 子组件生成函数keyGenerator?: (item: any, index: number) => string // 键值生成函数
): 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类型说明

interface IDataSource {totalCount(): number; // 获得数据总数getData(index: number): Object; // 获取索引值对应的数据registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
}

接口声明

参数类型

说明

totalCount(): number

-

获得数据总数。

getData(index: number): any

number

获取索引值index对应的数据。

index:获取数据对应的索引值。

registerDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注册数据改变的监听器。

listener:数据变化监听器

unregisterDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注销数据改变的监听器。

listener:数据变化监听器

DataChangeListener类型说明

interface DataChangeListener {onDataReloaded(): void; // 重新加载数据完成后调用onDataAdded(index: number): void; // 添加数据完成后调用onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用onDataDeleted(index: number): void; // 删除数据完成后调用onDataChanged(index: number): void; // 改变数据完成后调用onDataAdd(index: number): void; // 添加数据完成后调用onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用onDataDelete(index: number): void; // 删除数据完成后调用onDataChange(index: number): void; // 改变数据完成后调用
}

接口声明

参数类型

说明

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首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。

// Basic implementation of IDataSource to handle data listener
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}// 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}// 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}// 通知LazyForEach组件需要重载所有子组件notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}// 通知LazyForEach组件需要在index对应索引处添加子组件notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}// 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}// 通知LazyForEach组件需要在index对应索引处删除该子组件notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}// 通知LazyForEach组件将from索引和to索引处的子组件进行交换notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}@Entry
@Component
struct MyComponent {private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(`Hello ${i}`)}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: string) => {ListItem() {Row() {Text(item).fontSize(50).onAppear(() => {console.info("appear:" + item)})}.margin({ left: 10, right: 10 })}}, (item: string) => item)}.cachedCount(5)}
}

在上述代码中,键值生成规则是keyGenerator函数的返回值item。在LazyForEach循环渲染时,其为数据源数组项依次生成键值Hello 0、Hello 1 ... Hello 20,并创建对应的ListItem子组件渲染到界面上。

运行效果如下图所示。

图1 LazyForEach正常首次渲染

非首次渲染

当LazyForEach数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用listener对应的接口,通知LazyForEach做相应的更新,各使用场景如下。

  • 添加数据
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}@Entry
@Component
struct MyComponent {private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(`Hello ${i}`)}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: string) => {ListItem() {Row() {Text(item).fontSize(50).onAppear(() => {console.info("appear:" + item)})}.margin({ left: 10, right: 10 })}.onClick(() => {// 点击追加子组件this.data.pushData(`Hello ${this.data.totalCount()}`);})}, (item: string) => item)}.cachedCount(5)}
}

当我们点击LazyForEach的子组件时,首先调用数据源data的pushData方法,该方法会在数据源末尾添加数据并调用notifyDataAdd方法。在notifyDataAdd方法内会又调用listener.onDataAdd方法,该方法会通知LazyForEach在该处有数据添加,LazyForEach便会在该索引处新建子组件。

运行效果如下图所示。

图2 LazyForEach添加数据

  • 删除数据
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}public deleteData(index: number): void {this.dataArray.splice(index, 1);this.notifyDataDelete(index);}
}@Entry
@Component
struct MyComponent {private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(`Hello ${i}`)}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: string, index: number) => {ListItem() {Row() {Text(item).fontSize(50).onAppear(() => {console.info("appear:" + item)})}.margin({ left: 10, right: 10 })}.onClick(() => {// 点击删除子组件this.data.deleteData(this.data.dataArray.indexOf(item));})}, (item: string) => item)}.cachedCount(5)}
}

当我们点击LazyForEach的子组件时,首先调用数据源data的deleteData方法,该方法会删除数据源对应索引处的数据并调用notifyDataDelete方法。在notifyDataDelete方法内会又调用listener.onDataDelete方法,该方法会通知LazyForEach在该处有数据删除,LazyForEach便会在该索引处删除对应子组件。

运行效果如下图所示。

图3 LazyForEach删除数据

  • 交换数据
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}public deleteData(index: number): void {this.dataArray.splice(index, 1);this.notifyDataDelete(index);}public moveData(from: number, to: number): void {let temp: string = this.dataArray[from];this.dataArray[from] = this.dataArray[to];this.dataArray[to] = temp;this.notifyDataMove(from, to);}
}@Entry
@Component
struct MyComponent {private moved: number[] = [];private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(`Hello ${i}`)}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: string, index: number) => {ListItem() {Row() {Text(item).fontSize(50).onAppear(() => {console.info("appear:" + item)})}.margin({ left: 10, right: 10 })}.onClick(() => {this.moved.push(this.data.dataArray.indexOf(item));if (this.moved.length === 2) {// 点击移动子组件this.data.moveData(this.moved[0], this.moved[1]);this.moved = [];}})}, (item: string) => item)}.cachedCount(5)}
}

当我们首次点击LazyForEach的子组件时,在moved成员变量内存入要移动的数据索引,再次点击LazyForEach另一个子组件时,我们将首次点击的子组件移到此处。调用数据源data的moveData方法,该方法会将数据源对应数据移动到预期的位置并调用notifyDatMove方法。在notifyDataMove方法内会又调用listener.onDataMove方法,该方法通知LazyForEach在该处有数据需要移动,LazyForEach便会将from和to索引处的子组件进行位置调换。

运行效果如下图所示。

图4 LazyForEach交换数据

  • 改变单个数据
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}public deleteData(index: number): void {this.dataArray.splice(index, 1);this.notifyDataDelete(index);}public changeData(index: number, data: string): void {this.dataArray.splice(index, 1, data);this.notifyDataChange(index);}
}@Entry
@Component
struct MyComponent {private moved: number[] = [];private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(`Hello ${i}`)}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: string, index: number) => {ListItem() {Row() {Text(item).fontSize(50).onAppear(() => {console.info("appear:" + item)})}.margin({ left: 10, right: 10 })}.onClick(() => {this.data.changeData(index, item + '00');})}, (item: string) => item)}.cachedCount(5)}
}

当我们点击LazyForEach的子组件时,首先改变当前数据,然后调用数据源data的changeData方法,在该方法内会调用notifyDataChange方法。在notifyDataChange方法内会又调用listener.onDataChange方法,该方法通知LazyForEach组件该处有数据发生变化,LazyForEach便会在对应索引处重建子组件。

运行效果如下图所示。

图5 LazyForEach改变单个数据

  • 改变多个数据
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}public deleteData(index: number): void {this.dataArray.splice(index, 1);this.notifyDataDelete(index);}public changeData(index: number): void {this.notifyDataChange(index);}public reloadData(): void {this.notifyDataReload();}public modifyAllData(): void {this.dataArray = this.dataArray.map((item: string) => {return item + '0';})}
}@Entry
@Component
struct MyComponent {private moved: number[] = [];private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(`Hello ${i}`)}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: string, index: number) => {ListItem() {Row() {Text(item).fontSize(50).onAppear(() => {console.info("appear:" + item)})}.margin({ left: 10, right: 10 })}.onClick(() => {this.data.modifyAllData();this.data.reloadData();})}, (item: string) => item)}.cachedCount(5)}
}

当我们点击LazyForEach的子组件时,首先调用data的modifyAllData方法改变了数据源中的所有数据,然后调用数据源的reloadData方法,在该方法内会调用notifyDataReload方法。在notifyDataReload方法内会又调用listener.onDataReloaded方法,通知LazyForEach需要重建所有子节点。LazyForEach会将原所有数据项和新所有数据项一一做键值比对,若有相同键值则使用缓存,若键值不同则重新构建。

运行效果如下图所示。

图6 LazyForEach改变多个数据

  • 改变数据子属性

若仅靠LazyForEach的刷新机制,当item变化时若想更新子组件,需要将原来的子组件全部销毁再重新构建,在子组件结构较为复杂的情况下,靠改变键值去刷新渲染性能较低。因此框架提供了@Observed与@ObjectLink机制进行深度观测,可以做到仅刷新使用了该属性的组件,提高渲染性能。开发者可根据其自身业务特点选择使用哪种刷新方式。

class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: StringData[] = [];public totalCount(): number {return 0;}public getData(index: number): StringData {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: StringData[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): StringData {return this.dataArray[index];}public addData(index: number, data: StringData): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: StringData): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}@Observed
class StringData {message: string;constructor(message: string) {this.message = message;}
}@Entry
@Component
struct MyComponent {private moved: number[] = [];@State data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(new StringData(`Hello ${i}`));}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: StringData, index: number) => {ListItem() {ChildComponent({data: item})}.onClick(() => {item.message += '0';})}, (item: StringData, index: number) => index.toString())}.cachedCount(5)}
}@Component
struct ChildComponent {@ObjectLink data: StringDatabuild() {Row() {Text(this.data.message).fontSize(50).onAppear(() => {console.info("appear:" + this.data.message)})}.margin({ left: 10, right: 10 })}
}

此时点击LazyForEach子组件改变item.message时,重渲染依赖的是ChildComponent的@ObjectLink成员变量对其子属性的监听,此时框架只会刷新Text(this.data.message),不会去重建整个ListItem子组件。

图7 LazyForEach改变数据子属性

常见使用问题

  • 渲染结果非预期
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}public deleteData(index: number): void {this.dataArray.splice(index, 1);this.notifyDataDelete(index);}
}@Entry
@Component
struct MyComponent {private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(`Hello ${i}`)}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: string, index: number) => {ListItem() {Row() {Text(item).fontSize(50).onAppear(() => {console.info("appear:" + item)})}.margin({ left: 10, right: 10 })}.onClick(() => {// 点击删除子组件this.data.deleteData(index);})}, (item: string) => item)}.cachedCount(5)}
}

图8 LazyForEach删除数据非预期

当我们多次点击子组件时,会发现删除的并不一定是我们点击的那个子组件。原因是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其index均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的index,其itemGenerator中的index并没有发生变化,所以删除结果和预期不符。

修复代码如下所示。

class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}public deleteData(index: number): void {this.dataArray.splice(index, 1);this.notifyDataDelete(index);}public reloadData(): void {this.notifyDataReload();}
}@Entry
@Component
struct MyComponent {private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(`Hello ${i}`)}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: string, index: number) => {ListItem() {Row() {Text(item).fontSize(50).onAppear(() => {console.info("appear:" + item)})}.margin({ left: 10, right: 10 })}.onClick(() => {// 点击删除子组件this.data.deleteData(index);// 重置所有子组件的index索引this.data.reloadData();})}, (item: string, index: number) => item + index.toString())}.cachedCount(5)}
}
  • 在删除一个数据项后调用reloadData方法,重建后面的数据项,以达到更新index索引的目的。

    图9 修复LazyForEach删除数据非预期

  • 重渲染时图片闪烁
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: StringData[] = [];public totalCount(): number {return 0;}public getData(index: number): StringData {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: StringData[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): StringData {return this.dataArray[index];}public addData(index: number, data: StringData): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: StringData): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}public reloadData(): void {this.notifyDataReload();}
}class StringData {message: string;imgSrc: Resource;constructor(message: string, imgSrc: Resource) {this.message = message;this.imgSrc = imgSrc;}
}@Entry
@Component
struct MyComponent {private moved: number[] = [];private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: StringData, index: number) => {ListItem() {Column() {Text(item.message).fontSize(50).onAppear(() => {console.info("appear:" + item.message)})Image(item.imgSrc).width(500).height(200)}.margin({ left: 10, right: 10 })}.onClick(() => {item.message += '00';this.data.reloadData();})}, (item: StringData, index: number) => JSON.stringify(item))}.cachedCount(5)}
}

图10 LazyForEach仅改变文字但是图片闪烁问题

在我们点击ListItem子组件时,我们只改变了数据项的message属性,但是LazyForEach的刷新机制会导致整个ListItem被重建。由于Image组件是异步刷新,所以视觉上图片会发生闪烁。为了解决这种情况我们应该使用@ObjectLink和@Observed去单独刷新使用了item.message的Text组件。

修复代码如下所示。

class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: StringData[] = [];public totalCount(): number {return 0;}public getData(index: number): StringData {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: StringData[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): StringData {return this.dataArray[index];}public addData(index: number, data: StringData): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: StringData): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}@Observed
class StringData {message: string;imgSrc: Resource;constructor(message: string, imgSrc: Resource) {this.message = message;this.imgSrc = imgSrc;}
}@Entry
@Component
struct MyComponent {@State data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: StringData, index: number) => {ListItem() {ChildComponent({data: item})}.onClick(() => {item.message += '0';})}, (item: StringData, index: number) => index.toString())}.cachedCount(5)}
}@Component
struct ChildComponent {@ObjectLink data: StringDatabuild() {Column() {Text(this.data.message).fontSize(50).onAppear(() => {console.info("appear:" + this.data.message)})Image(this.data.imgSrc).width(500).height(200)}.margin({ left: 10, right: 10 })}
}

图11 修复LazyForEach仅改变文字但是图片闪烁问题

@ObjectLink属性变化UI未更新

class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: StringData[] = [];public totalCount(): number {return 0;}public getData(index: number): StringData {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: StringData[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): StringData {return this.dataArray[index];}public addData(index: number, data: StringData): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: StringData): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}@Observed
class StringData {message: NestedString;constructor(message: NestedString) {this.message = message;}
}@Observed
class NestedString {message: string;constructor(message: string) {this.message = message;}
}@Entry
@Component
struct MyComponent {private moved: number[] = [];@State data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: StringData, index: number) => {ListItem() {ChildComponent({data: item})}.onClick(() => {item.message.message += '0';})}, (item: StringData, index: number) => JSON.stringify(item) + index.toString())}.cachedCount(5)}
}@Component
struct ChildComponent {@ObjectLink data: StringDatabuild() {Row() {Text(this.data.message.message).fontSize(50).onAppear(() => {console.info("appear:" + this.data.message.message)})}.margin({ left: 10, right: 10 })}
}

图12 ObjectLink属性变化后UI未更新

@ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体请查看@ObjectLink与@Observed的详细使用方法和限制条件。

修复代码如下所示。

class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: StringData[] = [];public totalCount(): number {return 0;}public getData(index: number): StringData {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}class MyDataSource extends BasicDataSource {private dataArray: StringData[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): StringData {return this.dataArray[index];}public addData(index: number, data: StringData): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: StringData): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}@Observed
class StringData {message: NestedString;constructor(message: NestedString) {this.message = message;}
}@Observed
class NestedString {message: string;constructor(message: string) {this.message = message;}
}@Entry
@Component
struct MyComponent {private moved: number[] = [];@State data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: StringData, index: number) => {ListItem() {ChildComponent({data: item})}.onClick(() => {item.message = new NestedString(item.message.message + '0');})}, (item: StringData, index: number) => JSON.stringify(item) + index.toString())}.cachedCount(5)}
}@Component
struct ChildComponent {@ObjectLink data: StringDatabuild() {Row() {Text(this.data.message.message).fontSize(50).onAppear(() => {console.info("appear:" + this.data.message.message)})}.margin({ left: 10, right: 10 })}
}

图13 修复ObjectLink属性变化后UI更新

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/838072.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

上海交大携手阿里巴巴成立人工智能与系统联合实验室

5 月 8 日&#xff0c;上海交通大学电子信息与电气工程学院&#xff08;简称电院&#xff09;与阿里巴巴集团宣布共同成立人工智能与系统联合实验室&#xff08;后称“联合实验室”&#xff09;&#xff0c;并在上海交大闵行校区举行了揭牌仪式。 上海交大电院副院长王贺升教授…

python实现图片模式转换成素描模式

欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一.前言 二.代码 三.使用 四.分析 一.前言 素描,是一种用线条来描绘物体、场景或人物的绘画方式,它可以是简单的线条勾勒,也可以是复杂的光影渲染。这种艺术形式主要依赖线条和明暗关系来表现形态、空

PyTorch进行CIFAR-10图像分类

本节将通过一个实战案例来详细介绍如何使用PyTorch进行深度学习模型的开发。我们将使用CIFAR-10图像数据集来训练一个卷积神经网络。 神经网络训练的一般步骤如图5-3所示。 &#xff08;1&#xff09;加载数据集&#xff0c;并做预处理。 &#xff08;2&#xff09;预处理后的…

sin^2(x) 的图像

[TOC](sin^2(x) 的图像) 正文 这里记录一下 s i n 2 x sin^2{x} sin2x 的图像。 函数值以正弦的形式在 [0, 1] 区间内波动。 如果大家觉得有用&#xff0c;就点个赞让更多的人看到吧~

嵌入式文件系统

嵌入式文件系统 文件系统简介 在计算机系统中&#xff0c; 需要用到大量的程序和数据&#xff0c; 它们大部分以文件的形式存放在外部存储当中&#xff0c; 根据需要可随时调入内存使用 如果用户直接管理外存文件所面临的问题&#xff1a; 必须熟悉外存的物理特性了解各种存…

英语新概念2-回译法-lesson14

第一次回译 I had a amusing experience. I kept driving to the next town when I left a small village in the south of Franch.A teenager waved to me in the path.I stopped my car, he asked me to give him a lift.As soon as he get my car,I say good morning in Fr…

【Kubernetes】污点、容忍度、亲和性、调度和重启策略

标签、污点、容忍度、亲和性 一、标签1、定义2、给资源打标签【1】给Pod打标签【2】给Service打标签【3】给Node打标签 3、查看资源标签 二、节点选择器1、nodeName2、nodeSelector 三、污点、容忍度、亲和性1、node节点亲和性【1】硬亲和性【2】软亲和性 2、pod节点亲和性【1】…

安泰电子电压放大器应用及示例是什么样的

电压放大器是电子电路中常用的一种器件&#xff0c;用于将输入信号的电压放大至所需的输出电压。它在许多领域中有着重要的应用&#xff0c;包括通信、音频放大、仪器测量等。以下是电压放大器的一些应用及示例&#xff1a; 信号处理&#xff1a;在许多电子系统中&#xff0c;需…

Kasawaki川崎机器人故障维修

在当今的自动化工业领域&#xff0c;川崎工业机器人以其卓越的性能和可靠的工作效率赢得了广泛的赞誉。作为机器人的核心组成部分&#xff0c;伺服电机的作用至关重要。然而&#xff0c;就像所有机械设备一样&#xff0c;也可能会遭遇电机磨损或故障&#xff0c;需要适时的川崎…

vue自定义权限指令

定义v-hasPermi指令 /*** v-hasPermi 操作权限处理*/import useUserStore from /store/modules/userexport default {mounted(el, binding, vnode) {const { value } bindingconst all_permission "*:*:*";const permissions useUserStore().permissions&#xff…

linux - 搭建部署ftp服务器

ftp 服务: 实现ftp功能的一个服务,安装vsftpd软件搭建一台ftp服务器 ftp协议: 文件传输协议 (file transfer protocol),在不同的机器之间实现文件传输功能, 例如 视频文件下载,源代码文件下载 公司内部:弄一个专门的文件服务器,将公司里的文档资料和视频都存放…

基于死区补偿的永磁同步电动机矢量控制系统simulink仿真模型

整理了基于死区补偿的永磁同步电动机矢量控制系统simulink仿真&#xff0c;该模型使用线性死区补偿的PMSM矢量控制算法进行仿真&#xff0c;使用Foc电流双闭环 。 1.模块划分清晰&#xff0c;补偿前后仿真有对比&#xff0c;易于学习; 2.死区补偿算法的线性区区域可调; 3.自…

5.13网络编程

只要在一个电脑中的两个进程之间可以通过网络进行通信那么拥有公网ip的两个计算机的通信是一样的。但是一个局域网中的两台电脑上的虚拟机是不能进行通信的&#xff0c;因为这两个虚拟机在电脑中又有各自的局域网所以通信很难实现。 socket套接字是一种用于网络间进行通信的方…

Python接口自动化测试之动态数据处理

在前面的知识基础上介绍了在接口自动化测试中&#xff0c;如何把数据分离出来&#xff0c;并且找到它的共同点&#xff0c;然后依据这个共同点来找到解决复杂问题的思想。我一直认为&#xff0c;程序是人设计的&#xff0c;它得符合人性&#xff0c;那么自动化测试的&#xff0…

自由职业是种怎样的体验?普通人如何成为一名自由职业者?

自由职业在哪都能办公自由职业在哪都要办公。 放弃幻想&#xff0c;没有不辛苦的工作&#xff0c;5年经验后端开发程序员&#xff0c;已经从事自由职业1年半&#xff0c;今天就来客观分享一下自由职业的利与弊。 时间自由&#xff0c;减少中间商赚差价 自由职业最让人羡慕的就…

React Native 开发心得分享

有一段时间没更新了&#xff0c;花了点时间研究了下 React Native&#xff08;后续用 RN 简称&#xff09;&#xff0c;同时也用该技术作为我的毕设项目(一个校园社交应用&#xff0c;仿小红书)&#xff0c;经过了这段时间的疯狂折腾&#xff0c;对 RN 生态有了一定的了解&…

图搜索算法-最小生成树问题-普里姆算法(prim)

相关文章&#xff1a; 数据结构–图的概念 图搜索算法 - 深度优先搜索法&#xff08;DFS&#xff09; 图搜索算法 - 广度优先搜索法&#xff08;BFS&#xff09; 图搜索算法 - 拓扑排序 图搜索算法-最短路径算法-戴克斯特拉算法 图搜索算法-最短路径算法-贝尔曼-福特算法 图搜索…

【069】基于SpringBoot+Vue实现的企业资产管理系统

系统介绍 基于SpringBootVue实现的企业资产管理系统管理员功能有个人中心&#xff0c;用户管理&#xff0c;资产分类管理&#xff0c;资产信息管理&#xff0c;资产借出管理&#xff0c;资产归还管理&#xff0c;资产维修管理。用户可以对资产进行借出和归还操作。因而具有一定…

软考-下午题-试题一

1、概念 2、答题技巧和规范 问题1、2&#xff1a;直接看 格式&#xff1a; 问题3&#xff1a; 格式&#xff1a; 3、例题 eg2&#xff1a;可以以后写完问题4之后&#xff0c;把问题3补充完整 问题4&#xff1a; 问题4 官方解释&#xff1a; 问题4&#xff08;3‘&#xff09; 2…

在做题中学习(58):和为K的子数组

560. 和为 K 的子数组 - 力扣&#xff08;LeetCode&#xff09; 因为是判断子数组的和 要返回 k 的次数&#xff0c;所以 解法&#xff1a;前缀和 哈希表 提出一个概念&#xff1a;以下标i为结尾的所有子数组 那要找出所有和 k的子数组 就相当于&#xff1a;找出所有值为…