使用Repeat加载Text组件，内容渲染顺序出错如何解决
=============================

#### 问题现象

使用Repeat懒加载N个带样式的Text组件，当列表向下滑动到底部后，向上滑动，渲染的内容出现错误如何解决。问题代码如下：

import { LengthMetrics } from '@kit.ArkUI';


@Entry
@ComponentV2
struct Index {
  textBlockList: string\[\] = \[\];
  styledStringList: MutableStyledString\[\] = \[\];
  controllerList: TextController\[\] = \[\];


  aboutToAppear(): void {
    for (let i = 0; i < 200; i++) {
      let str: string = \`第${i}条：测试数据 index=${i}\`;
      let styledString = new MutableStyledString(str);
      let startStringMatch = str.match(/^第.+?条/); _// 匹配第x条，加粗_
      if (startStringMatch) {
        styledString.setStyle({
          start: 0,
          length: startStringMatch\[0\].length,
          styledKey: StyledStringKey.FONT,
          styledValue: new TextStyle({ fontWeight: 'bold' })
        });
      }
      styledString.setStyle({
        start: 0,
        length: str.length,
        styledKey: StyledStringKey.PARAGRAPH\_STYLE,
        styledValue: new ParagraphStyle({ textIndent: new LengthMetrics(36) })
      });
      let controller = new TextController();
      this.textBlockList.push(str);
      this.styledStringList.push(styledString);
      controller.setStyledString(styledString);
      this.controllerList.push(controller);
    }
  }


  build() {
    Column() {
      List() {
        Repeat<string>(this.textBlockList)
          .each((obj: RepeatItem<string>) => {
            ListItem() {
              Text(obj.item, { controller: this.controllerList\[obj.index\] })
                .width('90%')
                .fontSize(18)
                .copyOption(CopyOptions.LocalDevice)
                .lineHeight(26)
                .height('auto');
            }
            .onAppear(() => {
              console.info(\`加载了index=${obj.index}\`);
            });
          })
          .virtualScroll({ totalCount: this.textBlockList.length });
      }
      .cachedCount(2);
    };
  }
}

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/af/v3/7QC6FoVSSKGYCLc8LBcpFw/zh-cn_image_0000002628552618.gif?HW-CC-KV=V1&HW-CC-Date=20260709T133908Z&HW-CC-Expire=86400&HW-CC-Sign=853B37D70DC6AD5121119DD268CE7B8561DE8B6523CE53595E921C0101B2CCCD "点击放大")

#### 背景知识

*   [Repeat](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-new-rendering-control-repeat)：可复用的循环渲染。基于数组类型数据来进行循环渲染，一般与容器组件配合使用。
*   [Text](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-text)：显示一段文本的组件。文本在组件区域显示效果与字体资源相关，默认字体排印可见[字体排印视觉指引](https://developer.huawei.com/consumer/cn/doc/design-guides/font-0000001828772001)。
*   [TextController](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-text#textcontroller11)：Text组件的控制器。可以通过[setStyledString](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-text#setstyledstring12)接口触发绑定或更新属性字符串。多次调用setStyledString，会用新的入参覆盖已绑定的属性字符串，而不是叠加新的入参。属性字符串通过controller绑定时，需要等待布局完成后，绑定生效。

#### 问题定位

根据问题现象按照如下步骤进行定位：

1.  判断是否是复用和懒加载引起的问题，将Repeat改成ForEach发现显示正常，怀疑问题是在懒加载处理过程中引入。
2.  观察问题现象，首次渲染时Text文本样式都正常，但当组件滑出可视区域外时，再往回滑动渲染的样式和文字出现问题。怀疑是组件复用过程中，复用的Text绑定的还是原有的样式，样式修改未生效。

#### 分析结论

首次渲染时TextController和Text进行了绑定，组件复用时需要使用setStyledString接口更新属性字符串，只替换TextOptions中的controller实际并没有生效。而定位过程中使用ForEach正常，是因为ForEach会全量渲染，首次渲染就将全量的TextController和Text进行了绑定，之后不存在修改TextController引起的更新未生效问题。

#### 修改建议

建议在Text组件的onAppear回调函数中进行更新，确保每次渲染都能使用正确的属性字符串。

完整代码如下：

import { LengthMetrics } from '@kit.ArkUI';


@Entry
@ComponentV2
struct TextOrder {
  textBlockList: string\[\] = \[\];
  styledStringList: MutableStyledString\[\] = \[\];
  controllerList: TextController\[\] = \[\];


  aboutToAppear(): void {
   _ // 创建测试数据_
    for (let i = 0; i < 200; i++) {
      let str: string = \`第${i}条：测试数据 index=${i}\`;
      let styledString = new MutableStyledString(str);
      let startStringMatch = str.match(/^第.+?条/); _// 匹配第x条，加粗_
      _// 使用正则表达式匹配文本开头的"第x条"部分_
      if (startStringMatch) {
        styledString.setStyle({
          start: 0,
          length: startStringMatch\[0\].length,
          styledKey: StyledStringKey.FONT,
          styledValue: new TextStyle({ fontWeight: 'bold' })
        });
      }
     _ // 为整段文本设置段落样式_
      styledString.setStyle({
        start: 0,
        length: str.length,
        styledKey: StyledStringKey.PARAGRAPH\_STYLE,
        styledValue: new ParagraphStyle({ textIndent: new LengthMetrics(36) })
      });
     _ // 创建文本控制器_
      let controller = new TextController();
     _ // 将数据添加到对应的数组中_
      this.textBlockList.push(str);
      this.styledStringList.push(styledString);
      this.controllerList.push(controller);
    }
  }


  build() {
    Column() {
      List() {
        Repeat<string>(this.textBlockList)
          .each((obj: RepeatItem<string>) => {
            ListItem() {
              Text(obj.item, { controller: this.controllerList\[obj.index\] })
                .width('90%')
                .fontSize(18)
                .copyOption(CopyOptions.LocalDevice)
                .lineHeight(26)
                .height('auto')
            _    // 当文本组件出现时，为其设置样式_
                .onAppear(() => {
                  this.controllerList\[obj.index\].setStyledString(this.styledStringList\[obj.index\]);
                });
            }
            .onAppear(() => {
              console.info(\`加载了index=${obj.index}\`);
            });
          })
          .virtualScroll({ totalCount: this.textBlockList.length });
      }
      .cachedCount(2);
    };
  }
}

#### 常见FAQ

Q：使用Repeat渲染列表，是否有动态预加载的功能？

A：可以考虑使用Repeat的virtualScroll模式。Repeat根据容器组件的显示区域和预加载区域加载子组件。当容器滑动/数组改变时，Repeat会根据父容器组件的布局过程重新计算显示区域和预加载区域范围，并管理列表子组件节点的创建与销毁。

Q：在Repeat渲染列表时，如何判断virtualScroll模式下，懒加载是否生效？

A：可以在Repeat所渲染的列表项中添加[onAppear](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-events-show-hide#onappear)事件进行监听。通过在onAppear中打印日志，并对比日志输出数量与列表总条目数，即可判断懒加载是否实际生效。