用户3111234
用户3111234
  • 发布:2026-07-18 19:41
  • 更新:2026-07-18 19:44
  • 阅读:55

新请求数据和上次数据相同时LazyForEach内子组件不更新的问题如何解决

分类:uts

问题现象
如何实现两次请求返回数据完全相同时也刷新LazyForEach相关的组件。因为,在第一次请求返回数据进行了业务逻辑处理和相关组件的刷新;第二次请求返回数据时,需要重置组件的状态。如果直接使用返回的相关数据作为LazyForEach的键值,不会触发相关组件的状态刷新,如何在数据完全相同时也刷新LazyForEach相关的组件?

背景知识
键值生成规则:在LazyForEach循环渲染过程中,系统会为每个item生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
LazyForEach提供了keyGenerator参数,用于传入键值生成函数,可以通过它自定义键值的生成规则。如果没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即:(item: any, index: number) => { return viewId + '-' + index.toString(); }
组件创建规则:在确定键值生成规则后,LazyForEach的第二个参数itemGenerator函数会根据组件创建规则为数据源的每个数组项创建组件。组件的创建包括两种情况:LazyForEach首次渲染和LazyForEach数据更新后的非首次渲染。
解决方案
LazyForEach渲染的原理:只有当keyGenerator参数定义的键值发生变化时,ArkUI框架才会将该数组元素视为已被替换或修改,并会基于新的键值创建一个新的组件。

上述问题中,两次请求返回的数据完全相同时,使用返回数据作为键值将不会触发组件刷新。而业务要求只要有新的请求数据,就必须刷新对应的UI组件,即使数据是相同的。因此需要定义一个合适的keyGenerator参数,对相同元素也能产生不同的键值。

采用在键值生成中加入随机数的方法,示例代码如下:

import { util } from '@kit.ArkTS';

@Entry
@Component
struct RefreshComponentsWhenDataIdentical {
@State bol: boolean = true;
@State debtData: CommonDataSource<Data> = new CommonDataSource([]);
data1: Data[] = [new Data('分组1 name1', '分组1 code1'), new Data('分组1 name2', '分组1 code2')];

aboutToAppear(): void {
this.debtData.setNewData(this.data1);
}

build() {
Column() {
Button('重新请求数据')
.width('60%')
.height(40)
.fontColor(Color.White)
.onClick(() => {
this.getDetData();
})
.margin({ top: 16, bottom: 16 });
List() {
LazyForEach(this.debtData, (data: Data) => {
ListItem() {
TestComponent({ data: data })
.margin({ left: 16, right: 16 });
}
.height(80)
.onClick(() => {
data.stockCode = data.stockCode + 'ss';
});
}, (value: Data) => JSON.stringify(value) + util.generateRandomUUID(false));
}
.width('100%')
.layoutWeight(1)
.listDirection(Axis.Vertical)
.divider({ strokeWidth: 5, color: 'app.color.dz_root_background' });
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center);
}

// 获取数据
getDetData() {
if (this.bol) {
this.bol = false;
this.debtData.setNewData([new Data('分组1 name1', '分组1 code1'), new Data('分组1 name2', '分组1 code2')]);
} else {
this.bol = true;
this.debtData.setNewData([new Data('分组1 name1', '分组1 code1'), new Data('分组1 name2', '分组1 code2')]);
}
}
}

@Component
export struct TestComponent {
@ObjectLink data: Data;

build() {
Text(this.data.stockCode).fontColor(Color.Black).fontSize(20);
}
}

@Observed
export class Data {
stockName: string = '';
stockCode: string = '';

constructor(name: string, code: string) {
this.stockName = name;
this.stockCode = code;
}
}

export class CommonDataSource<T> implements IDataSource {
private dataArray: T[] = [];
private listeners: DataChangeListener[] = [];

constructor(element: T[]) {
this.dataArray = element;
}

// 获取数据
public getData(index: number) {
return this.dataArray[index];
}

public getDataList() {
return this.dataArray;
}

public totalCount(): number {
return this.dataArray.length;
}

public getIndex(data: T): number {
return this.dataArray.indexOf(data);
}

// 添加数据
public addArrayData(data: T[]): void {
this.dataArray = this.dataArray.concat(data);
this.notifyDataReload();
}

public setNewData(data: T[]): void {
this.dataArray = [];
this.addArrayData(data);
}

public addData(index: number, data: T[]): void {
this.dataArray = this.dataArray.concat(data);
this.notifyDataAdd(index);
}

public pushData(data: T): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}

// 清除数据
public clearData(): void {
this.dataArray = [];
}

// 重新加载数据
public refresh(): void {
this.notifyDataReload();
}

unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1);
}
}

registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}

notifyDataReload(): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataReloaded();
});
}

notifyDataAdd(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataAdd(index);
});
}

notifyDataChange(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataChange(index);
});
}

notifyDataDelete(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataDelete(index);
});
}

notifyDataMove(from: number, to: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataMove(from, to);
});
}
}

https://pastebin.com/LG9KPfDf
https://pastebin.com/sp4V1Lt6
https://pastebin.com/fRtXJrJy
https://pastebin.com/DwfDbqnK
https://pastebin.com/rB2TDtc1
https://pastebin.com/RMfBLWqq
https://pastebin.com/NPBvC2FU
https://pastebin.com/3zzXLnqF
https://pastebin.com/igVpzVQh
https://pastebin.com/g1V6bGBC
https://pastebin.com/BfvLFKLj
https://pastebin.com/C1LVHavn
https://pastebin.com/EXZZTVBg
https://pastebin.com/dbc5pW1L
https://pastebin.com/3NrbP8AA
https://pastebin.com/SpM2qh17
https://pastebin.com/Nsg9E303
https://pastebin.com/CXsUzneV
https://pastebin.com/9aiztZEu
https://pastebin.com/9HN5m9n1
https://pastebin.com/BW6QUixL
https://pastebin.com/4Gcwmf5Q
https://pastebin.com/eJeVNBWr
https://pastebin.com/jn1cgKbv
https://pastebin.com/6s977exs
https://pastebin.com/Akq8pgpd
https://pastebin.com/rBKWm24f
https://pastebin.com/M3uqGdJL
https://pastebin.com/PUdvCTp9
https://pastebin.com/S98rrTUM
https://pastebin.com/HQ9hJiDq
https://pastebin.com/JdhyRKfK
https://pastebin.com/rFzfBHud
https://pastebin.com/1Ggvt99f
https://pastebin.com/z5ENx2Ce
https://pastebin.com/6J6T1SZW
https://pastebin.com/yiqH2VzK
https://pastebin.com/yKd27fyP
https://pastebin.com/390CVwZN
https://pastebin.com/MCQTXY1N
https://pastebin.com/ardTpzv8
https://pastebin.com/wsWM0W3S
https://pastebin.com/aqjJUeAh
https://pastebin.com/BM9vxRKx
https://pastebin.com/tZmv7MCT
https://pastebin.com/sutbR6J2
https://pastebin.com/etgzExek
https://pastebin.com/a8W46JPg
https://pastebin.com/Rp4LDvur
https://pastebin.com/gBa7YY8h
https://pastebin.com/eH5rjQL4
https://pastebin.com/7DiNeMFM
https://pastebin.com/C9f93cWW
https://pastebin.com/MmA9JNEu
https://pastebin.com/1jRhG6vN
https://pastebin.com/AmK1z6bL
https://pastebin.com/k0fiFJFz
https://pastebin.com/HUvnH87n
https://pastebin.com/QcEh13s1
https://pastebin.com/TPALhndt
https://pastebin.com/SurRpKRQ
https://pastebin.com/ky0r7r9Y
https://pastebin.com/8Kmr3CSf
https://pastebin.com/Z3vg7Q7H
https://pastebin.com/Wb2fwRrq
https://pastebin.com/2u38GtWL
https://pastebin.com/HQm5GMt8
https://pastebin.com/iWiWmi6r
https://pastebin.com/jbdyP36z
https://pastebin.com/wNqvLyLk
https://pastebin.com/cNiKN9Yy
https://pastebin.com/dYBCAPXX
https://pastebin.com/QN4ZeWbN
https://pastebin.com/mPgv1YUc
https://pastebin.com/9exFu4Wb
https://pastebin.com/xmmKJ9kK
https://pastebin.com/zK4vc8aP
https://pastebin.com/MJf1eRRu
https://pastebin.com/m0H0k0fu
https://pastebin.com/E2vv7thB
https://pastebin.com/ch7be9cJ
https://pastebin.com/4CFChmU5
https://pastebin.com/beQzZHzD
https://pastebin.com/t1J9da3s
https://pastebin.com/RLDdYFVJ
https://pastebin.com/2KPVLtvg
https://pastebin.com/j3zzKggg
https://pastebin.com/tWJutyu6
https://pastebin.com/SeBBFwn6
https://pastebin.com/gBsERmYM
https://pastebin.com/G9W5Rass
https://pastebin.com/MqeeMkDs
https://pastebin.com/xXP1gyeN
https://pastebin.com/3XDcdSkZ
https://pastebin.com/qwy2GzNc
https://pastebin.com/00uWF4E9
https://pastebin.com/UEPHukb3
https://pastebin.com/47kzpsep
https://pastebin.com/cv9yuzg7
https://pastebin.com/xuMdNpi4
https://pastebin.com/Y2SAFNCv
https://pastebin.com/Xq8Gsg49
https://pastebin.com/hpXpJtvg
https://pastebin.com/jn6wwRt0
https://pastebin.com/T6DiqV32
https://pastebin.com/ZprFcr5d
https://pastebin.com/QMsYrh18
https://pastebin.com/Vv2MeGmu
https://pastebin.com/mcJLrWMy
https://pastebin.com/CgMwRtha
https://pastebin.com/DUp6GQ76
https://pastebin.com/fpwnWyPs
https://pastebin.com/6iM3Axa9
https://pastebin.com/mxkARAJj
https://pastebin.com/GjmpRkWu
https://pastebin.com/4XtWL2ei
https://pastebin.com/UA8k6zLY
https://pastebin.com/1W384Jp2
https://pastebin.com/gg9PnYZA
https://pastebin.com/zJXPXf2i
https://pastebin.com/4gfWnL3q
https://pastebin.com/MM3w3kuB
https://pastebin.com/F7hiitks
https://pastebin.com/5VSDMC8d
https://pastebin.com/ZVgeRh8s
https://pastebin.com/W2jebKZF
https://pastebin.com/NBVvNUyT
https://pastebin.com/SHfi6if3
https://pastebin.com/UZRaW8yX
https://pastebin.com/84vzCM9x
https://pastebin.com/gzrPNFzD
https://pastebin.com/MYAj784U
https://pastebin.com/FvvL2rG7
https://pastebin.com/9S0W1NqK
https://pastebin.com/WEwAYj2i
https://pastebin.com/KytXXuX9
https://pastebin.com/jPTEgVGN
https://pastebin.com/J10kPxex
https://pastebin.com/Hjhk4Dz3
https://pastebin.com/bFjm4rUr
https://pastebin.com/vDR5Y1n9
https://pastebin.com/ryRsCpx4
https://pastebin.com/kQdD2BQm
https://pastebin.com/8f5mtf0d
https://pastebin.com/jt3p07MP
https://pastebin.com/00MstLZA
https://pastebin.com/1YKqWeR3
https://pastebin.com/yaDjpbC6
https://pastebin.com/eSKu5vaw
https://pastebin.com/FP1FmgtV
https://pastebin.com/rQF1wHzR
https://pastebin.com/kWqHfTvC
https://pastebin.com/xhamgHHa
https://pastebin.com/S6JvSYeF
https://pastebin.com/pb1yY9Xz
https://pastebin.com/J5wPXm6c
https://pastebin.com/kzgLcrHB
https://pastebin.com/3PQENX2F
https://pastebin.com/etPfSnB4
https://pastebin.com/djN5sYaH
https://pastebin.com/M346nJqH
https://pastebin.com/ajdFspS1
https://pastebin.com/BN5HmF08
https://pastebin.com/QL4Dbmrf
https://pastebin.com/Ybqcyu6f
https://pastebin.com/u1vkjg13
https://pastebin.com/CP4F7eWb
https://pastebin.com/S3FBs1zh
https://pastebin.com/w89RRWgJ
https://pastebin.com/LGquhnCn
https://pastebin.com/sbVkq9Td
https://pastebin.com/difx3Jd1
https://pastebin.com/fQJ5f0fE
https://pastebin.com/iLes2G95
https://pastebin.com/xEjj2xn0
https://pastebin.com/ex4STLzj
https://pastebin.com/6Q0cFYwx
https://pastebin.com/tSpUw60k
https://pastebin.com/8K3Meijc
https://pastebin.com/c2nqu6jT
https://pastebin.com/LXvZtirn
https://pastebin.com/rDLzSpdC
https://pastebin.com/HCYMf3v2
https://pastebin.com/fq4jbH7Z
https://pastebin.com/NNQXDYBk
https://pastebin.com/P8gG50uY
https://pastebin.com/XYg6eetm
https://pastebin.com/MWxS1c5q
https://pastebin.com/vDZKWwvT
https://pastebin.com/GVLE6ARe
https://pastebin.com/UNBPGN4r
https://pastebin.com/wkcCqvNb
https://pastebin.com/T2qBEWE4
https://pastebin.com/M7Tj8RZx
https://pastebin.com/bAJKiNSL
https://pastebin.com/BFPrPePk
https://pastebin.com/kzNAhXjL
https://pastebin.com/WPqgzL0X
https://pastebin.com/SCgL51qp
https://pastebin.com/xHjRRfW8
https://pastebin.com/Ajf7XCsr
https://pastebin.com/J1zhmYZp
https://pastebin.com/FiZVzhEd
https://pastebin.com/4rQj22P6
https://pastebin.com/ygHC8n0W
https://pastebin.com/bjTHFxzh
https://pastebin.com/NC7C5BkK
https://pastebin.com/mnvce1uh
https://pastebin.com/vGc4SDXv
https://pastebin.com/ZzguJPTw
https://pastebin.com/jXrB5zwk
https://pastebin.com/zZqitQT4
https://pastebin.com/nh6aEYvs
https://pastebin.com/KR1qhhdM
https://pastebin.com/s5Sf2UtU
https://pastebin.com/ZxXZkH4K
https://pastebin.com/Mmw8Kag7
https://pastebin.com/bh8PkM6r
https://pastebin.com/Uj3J3hfW
https://pastebin.com/Tm2XV2Y5
https://pastebin.com/577MyzD0
https://pastebin.com/DfefxfPD
https://pastebin.com/Er9XQs5d
https://pastebin.com/FsTsUxeJ
https://pastebin.com/RV3qQXaf
https://pastebin.com/xSFujpYG
https://pastebin.com/W1pRFPLH
https://pastebin.com/gxVjNU8G
https://pastebin.com/ed4mgnP4
https://pastebin.com/wtYe0131
https://pastebin.com/XpqU01nc
https://pastebin.com/VJeLLMHE
https://pastebin.com/HWFJd2qJ
https://pastebin.com/MPb9dq8R
https://pastebin.com/H9H1TGiX
https://pastebin.com/h2DUsQgq
https://pastebin.com/GFSTTJmX
https://pastebin.com/Es1WDfxP
https://pastebin.com/ppt4P4qk
https://pastebin.com/nHQE3TRL
https://pastebin.com/bpNMtNMW
https://pastebin.com/Hq5FTEjV
https://pastebin.com/EbX9Z1pq
https://pastebin.com/Py47mduA
https://pastebin.com/NN9gZqqr
https://pastebin.com/ZVKWs4SK
https://pastebin.com/HTTgh7zf
https://pastebin.com/AdtyEWFH
https://pastebin.com/WugCjUHz
https://pastebin.com/m65SDVYc
https://pastebin.com/RaJSNB8a
https://pastebin.com/yjtkVTaz
https://pastebin.com/9pf1SzY1
https://pastebin.com/4eBzVMUG
https://pastebin.com/F9QaupTa
https://pastebin.com/V14QWPut
https://pastebin.com/fHycmT9w
https://pastebin.com/tQJRfczk
https://pastebin.com/xNCEBaCw
https://pastebin.com/ybLxWLev
https://pastebin.com/skF0ugu3
https://pastebin.com/f3s7TNHT
https://pastebin.com/Bkjn2MWk
https://pastebin.com/W2c1jW71
https://pastebin.com/vCiMsURq
https://pastebin.com/SJCJTac0
https://pastebin.com/D4dhYWX9
https://pastebin.com/FsnL9LKu
https://pastebin.com/AWJd5SmL
https://pastebin.com/JcP5W6s5
https://pastebin.com/EUPQHS98
https://pastebin.com/3TJpLjyB
https://pastebin.com/wtceVQ31
https://pastebin.com/frxrDb0F
https://pastebin.com/U87x3eA3
https://pastebin.com/Qg51EKjg
https://pastebin.com/h88NjhBR
https://pastebin.com/jfP9P1s8
https://pastebin.com/tmBJUH8S
https://pastebin.com/daEt4AL4
https://pastebin.com/rwUpkbSh
https://pastebin.com/VdcjusAf
https://pastebin.com/9LU0VEj1
https://pastebin.com/DTY4dDGG
https://pastebin.com/YTRyE1nw
https://pastebin.com/LeZtq6Sj
https://pastebin.com/BEjfftNX
https://pastebin.com/AdA544ry
https://pastebin.com/rjy9teZb
https://pastebin.com/3xXptukV
https://pastebin.com/wKcz9rPJ
https://pastebin.com/XzUDrBN9
https://pastebin.com/9YD39DKi
https://pastebin.com/bViQpRje
https://pastebin.com/69JJZFg3
https://pastebin.com/28YAuC2z
https://pastebin.com/J0iyXVeW

2026-07-18 19:41 负责人:无 分享
已邀请:
Ask小助手

Ask小助手 - Ask社区AI助手

欢迎到专业群( uni-app x技术交流群 ) 咨询,群中有相关专业的管理员和群友。

在 uni-app x 中,列表渲染使用的是 Vue 语法的 v-for,而不是鸿蒙 ArkUI 的 LazyForEach。但两者的底层渲染引擎在组件复用机制上有相似的设计理念:都依赖唯一的键值(key)来决定是否复用已有的组件实例。如果两次请求返回的数据完全相同,且使用相同的键值,框架会判定组件无需更新,从而复用旧实例,导致组件内部状态未被重置。

要解决“新请求数据和上次数据相同时也刷新组件”的问题,直接在键值中拼接纯随机数(如 UUID 或 Math.random())虽然能强制刷新,但在 uni-app x 的长列表场景中会引发严重的性能灾难,因为这会破坏组件复用机制,导致所有列表项被销毁并重新创建,引发掉帧、白块甚至应用卡顿。

以下是符合 uni-app x 原生渲染与性能工程规范的解决方案:

方案一:在数据模型中增加版本号或时间戳(推荐)

在每次请求返回数据后,为数据对象赋予一个新的版本号(如递增计数器或当前时间戳),并将其作为 :key 的一部分。这样只有当数据真正重新请求时,键值才会变化,从而触发组件重建;而在正常的列表滚动和局部更新中,键值保持稳定,不影响复用性能。

示例代码(pages/list/index.uvue):

<template>  
  <view class="page">  
    <button @click="requestData" class="btn">重新请求数据</button>  
    <list-view style="flex: 1">  
      <list-item v-for="item in dataList" :key="item.id + '_' + item.version">  
        <test-component :data="item" />  
      </list-item>  
    </list-view>  
  </view>  
</template>  

<script setup lang="uts">  
  // 定义强类型数据结构  
  type DataItem = {  
    id : string  
    name : string  
    version : number // 用于刷新组件的版本号  
  }  

  const dataList = ref<DataItem[]>([])  

  // 模拟获取数据  
  const requestData = () => {  
    // 假设两次请求返回的原始业务数据完全相同  
    const newData: DataItem[] = [  
      { id: '1', name: '分组1 name1', version: Date.now() },  
      { id: '2', name: '分组1 name2', version: Date.now() }  
    ]  
    dataList.value = newData  
  }  

  onLoad(() => {  
    requestData()  
  })  
</script>  

<style>  
  .page {  
    flex: 1;  
  }  
  .btn {  
    margin: 16px;  
    background-color: #007aff;  
    color: #ffffff;  
  }  
</style>

本助手使用国产模型,生成代码准确率较低。强烈建议使用uni-agent生成代码

方案二:通过 ref 调用子组件方法重置状态(最佳实践)

如果业务要求只是“重置组件的状态”(如清空输入框、重置选中态),而不是重新创建整个组件,更推荐的做法是通过 ref 获取子组件实例,调用其暴露的重置方法。这样能最大程度保留组件复用的性能优势,避免不必要的销毁与重建。

子组件示例(components/test-component.uvue):

<template>  
  <view>  
    <text>{{ data.name }}</text>  
    <text>内部状态: {{ internalState }}</text>  
  </view>  
</template>  

<script setup lang="uts">  
  type DataItem = {  
    id : string  
    name : string  
  }  

  const props = defineProps<{  
    data : DataItem  
  }>()  

  const internalState = ref(0)  

  // 暴露重置方法给父组件调用  
  const resetState = () => {  
    internalState.value = 0  
  }  

  defineExpose({  
    resetState  
  })  
</script>

本助手使用国产模型,生成代码准确率较低。强烈建议使用uni-agent生成代码

父页面调用示例:

<template>  
  <list-view style="flex: 1">  
    <list-item v-for="(item, index) in dataList" :key="item.id">  
      <test-component :ref="(el) => setItemRef(el, index)" :data="item" />  
    </list-item>  
  </list-view>  
</template>  

<script setup lang="uts">  
  const itemRefs = ref<TestComponent[]>([])  

  const setItemRef = (el : TestComponent | null, index : number) => {  
    if (el !== null) {  
      itemRefs.value[index] = el  
    }  
  }  

  const requestData = () => {  
    // 获取数据后,遍历调用子组件的重置方法  
    itemRefs.value.forEach((comp) => {  
      comp.resetState()  
    })  
  }  
</script>

本助手使用国产模型,生成代码准确率较低。强烈建议使用uni-agent生成代码

工程注意事项

  1. 长列表性能优先级:在 uni-app x 中,长列表必须使用 list-viewlist-item 组件以获得原生级的节点复用能力。随意修改 :key 会导致节点无法复用,在数据量大时会引发严重的内存和性能问题。
  2. 避免纯随机 key:切勿在 :key 中直接使用 Math.random()util.generateRandomUUID(),这会导致每次数据更新时整个列表被全量重建。
  3. 蒸汽模式兼容性:上述方案同时适用于 VDOM 模式和蒸汽模式(Vapor)。在蒸汽模式下,由于去除了 VNode 创建开销,组件重建的性能损耗相对较小,但依然建议遵循复用优先的原则。
内容为 AI 生成,仅供参考

要回复问题请先登录注册