文档管理中心
FAQ应用框架开发UI框架组件使用如何使用template设置多个模板并解决渲染异常问题

如何使用template设置多个模板并解决渲染异常问题

问题现象

使用Repeat渲染长列表时,当列表项存在多种不同模板类型时,通常会使用渲染模板(template)进行处理。一般有下面几种常见的问题场景:

  • 场景一:模板数量多的情况如何处理?

    是否需要对每个模板都单独设置template,对于模板多的情况是否有其他方式减少重复设置template,例如下面场景:

    收起
    自动换行
    深色代码主题
    复制
    1. // ...
    2. Repeat<string>(this.message)
    3. .virtualScroll() // 开启虚拟滚动
    4. .each((item: RepeatItem<string>) => {
    5. // ...
    6. })
    7. .key((item: string, index: number) => {
    8. return item + '_' + index;
    9. })
    10. .templateId((item: string, index: number): string => {
    11. return item;
    12. })
    13. // template type渲染对应的template子组件
    14. .template('1', (item: RepeatItem<string>) => {
    15. // 模板1
    16. })
    17. .template('2', (item: RepeatItem<string>) => {
    18. // 模板2
    19. })
    20. .template('3', (item: RepeatItem<string>) => {
    21. // 模板3
    22. })
    23. .template('4', (item: RepeatItem<string>) => {
    24. // 模板4
    25. })
    26. .template('5', (item: RepeatItem<string>) => {
    27. // 模板5
    28. })
    29. // ...
    30. .template('100', (item: RepeatItem<string>) => {
    31. // 模板100
    32. });
  • 场景二:刷新数据源,数据项未按照设定模板进行渲染如何处理?

    首次渲染时,按照template type渲染对应的template子组件,但刷新数据源后,数据项的template type变化,对应的数据项未按照对应的template进行渲染,仍显示首次渲染的模板,问题代码如下:

    收起
    自动换行
    深色代码主题
    复制
    1. @Entry
    2. @ComponentV2
    3. struct RepeatPage {
    4. @Local message: string[] = ['1', '1', '1', '1', '1', '1', '1', '1'];
    5. build() {
    6. Column() {
    7. // 刷新数据源
    8. Button('refresh')
    9. .onClick(() => {
    10. this.message = ['2', '3', '2', '3', '2', '3', '2', '2', '3', '2', '3', '2', '3', '2'];
    11. })
    12. .margin({
    13. left: 16,
    14. top: 16,
    15. bottom: 16,
    16. right: 16
    17. });
    18. List({ space: 18 }) {
    19. Repeat<string>(this.message)
    20. .each((item: RepeatItem<string>) => {
    21. ListItem() {
    22. Text(`each: ${item.item}`)
    23. .backgroundColor('#f1f3f5')
    24. .margin({
    25. left: 16,
    26. right: 16
    27. });
    28. };
    29. })
    30. .key((item: string, index: number) => {
    31. return item + '_' + index;
    32. })
    33. .templateId((item: string, index: number): string => {
    34. console.info(`item = ${item}, index = ${index}`);
    35. return item;
    36. })
    37. // template type渲染对应的template子组件
    38. .template('1', (item: RepeatItem<string>) => {
    39. ListItem() {
    40. Text(`template_1: ${item.item}`)
    41. .backgroundColor('#f1f3f5')
    42. .margin({
    43. left: 16,
    44. right: 16
    45. });
    46. };
    47. }, { cachedCount: 2 })
    48. .template('2', (item: RepeatItem<string>) => {
    49. ListItem() {
    50. Text(`template_2: ${item.item}`)
    51. .backgroundColor('#f1f3f5')
    52. .margin({
    53. left: 16,
    54. right: 16
    55. });
    56. };
    57. }, { cachedCount: 2 });
    58. }
    59. .layoutWeight(1)
    60. .width('100%');
    61. };
    62. }
    63. }

背景知识

  • Repeat:可复用的循环渲染,基于数组类型数据来进行循环渲染,一般与滚动容器组件配合使用。
  • 循环渲染能力:Repeat子组件由.each()和.template()属性定义,只允许包含一个子组件。
  • each:组件生成函数。当所有.template()的type和.templateId()返回值不匹配(即当前item不适用任何template定义的样式)时,将使用.each()处理数据项。
  • virtualScroll:Repeat开启虚拟滚动。
  • templateId:为当前数据项分配template type。
  • template:由template type渲染对应的template子组件。
  • Repeat提供渲染模板(template)能力,可以在同一个数据源中渲染多种子组件。每个数据项会根据.templateId()得到template type,从而渲染type对应的.template()中的子组件。
  • if/else:条件渲染可根据应用状态,使用if、else和else if渲染相应的UI内容。
  • visibility:控制组件的显示或隐藏。当未设置visibility时,组件默认为显示。

解决方案

场景一:模板数量多的情况。

  1. Repeat的渲染模板(template)能力,需要通过.templateId()方法为每个数据项指定模板类型,并在.template()中定义对应的渲染逻辑。不同模板类型对应不同的子组件结构,Repeat会根据类型自动复用相同模板的组件节点。详细参考:循环渲染能力说明
  2. 若模板数量多时,不希望重复设置template定义多个模板,避免代码重复。可以考虑把结构相似的子组件放在一个template中处理,将数据项相关信息(例如item,index等)作为参数传给@Builder函数,在@Builder内使用visibility、if/else条件渲染等方式进行处理。例如通过visibility控制子组件内部组件的显示隐藏:
    收起
    自动换行
    深色代码主题
    复制
    1. @Entry
    2. @ComponentV2
    3. struct TemplatePageOne {
    4. @Local message: string[] = ['1', '2', '1', '2', '3', '3', '4', '4', '5', '5',];
    5. build() {
    6. Column() {
    7. List({ space: 18 }) {
    8. Repeat<string>(this.message)
    9. .virtualScroll() // 开启虚拟滚动
    10. .each((item: RepeatItem<string>) => {
    11. ListItem() {
    12. Text(`each: ${item.item}`)
    13. .backgroundColor('#f1f3f5')
    14. .margin({
    15. left: 16,
    16. right: 16
    17. });
    18. };
    19. })
    20. .key((item: string, index: number) => {
    21. return item + '_' + index;
    22. })
    23. .templateId((item: string, index: number): string => {
    24. console.info(`item = ${item}, index = ${index}`);
    25. if (item == '3' || item == '4') {
    26. return 'other';
    27. }
    28. return item;
    29. })
    30. // template type渲染对应的template子组件
    31. .template('1', (item: RepeatItem<string>) => {
    32. ListItem() {
    33. Text(`template_1: ${item.item}`)
    34. .backgroundColor('#f1f3f5')
    35. .margin({
    36. left: 16,
    37. right: 16
    38. });
    39. };
    40. }, { cachedCount: 2 })
    41. .template('2', (item: RepeatItem<string>) => {
    42. ListItem() {
    43. Text(`template_2: ${item.item}`)
    44. .backgroundColor('#f1f3f5')
    45. .margin({
    46. left: 16,
    47. right: 16
    48. });
    49. };
    50. }, { cachedCount: 2 })
    51. .template('other', (item: RepeatItem<string>) => {
    52. ListItem() {
    53. Row() {
    54. Text(`template_other: `)
    55. .backgroundColor('#f1f3f5')
    56. .margin({
    57. left: 16,
    58. right: 16
    59. });
    60. // 通过visibility控制组件显示隐藏
    61. Text(`this is template 3`)
    62. .backgroundColor('#f1f3f5')
    63. .visibility(item.item == '3' ? Visibility.Visible : Visibility.None)
    64. .margin({
    65. left: 16,
    66. right: 16
    67. });
    68. Text(`this is template 4`)
    69. .backgroundColor('#f1f3f5')
    70. .visibility(item.item == '4' ? Visibility.Visible : Visibility.None)
    71. .margin({
    72. left: 16,
    73. right: 16
    74. });
    75. }
    76. };
    77. }, { cachedCount: 2 });
    78. }
    79. .layoutWeight(1)
    80. .width('100%');
    81. };
    82. }
    83. }

场景二:刷新数据源,数据项未按照设定模板进行渲染。

  1. template渲染模板能力需要启用虚拟滚动(virtualScroll)。
  2. 当未开启virtualScroll()时,Repeat会一次性渲染全部数据项对应的组件,template的复用失效。详细参考:关闭懒加载
  3. 所以刷新数据源后,虽然template type变化,但是template的复用失效,仍显示首次渲染的模板。需要手动设置.virtualScroll(),完整示例如下:
    收起
    自动换行
    深色代码主题
    复制
    1. @Entry
    2. @ComponentV2
    3. struct TemplatePageTwo {
    4. @Local message: string[] = ['1', '1', '1', '1', '1', '1', '1', '1'];
    5. build() {
    6. Column() {
    7. // 刷新数据源
    8. Button('refresh')
    9. .onClick(() => {
    10. this.message = ['2', '3', '2', '3', '2', '3', '2', '2', '3', '2', '3', '2', '3', '2'];
    11. })
    12. .margin({
    13. left: 16,
    14. top: 16,
    15. bottom: 16,
    16. right: 16
    17. });
    18. List({ space: 18 }) {
    19. Repeat<string>(this.message)
    20. .virtualScroll() // 开启虚拟滚动
    21. .each((item: RepeatItem<string>) => {
    22. ListItem() {
    23. Text(`each: ${item.item}`)
    24. .backgroundColor('#f1f3f5')
    25. .margin({
    26. left: 16,
    27. right: 16
    28. });
    29. };
    30. })
    31. .key((item: string, index: number) => {
    32. return item + '_' + index;
    33. })
    34. .templateId((item: string, index: number): string => {
    35. console.info(`item = ${item}, index = ${index}`);
    36. return item;
    37. })
    38. // template type渲染对应的template子组件
    39. .template('1', (item: RepeatItem<string>) => {
    40. ListItem() {
    41. Text(`template_1: ${item.item}`)
    42. .backgroundColor('#f1f3f5')
    43. .margin({
    44. left: 16,
    45. right: 16
    46. });
    47. };
    48. }, { cachedCount: 2 })
    49. .template('2', (item: RepeatItem<string>) => {
    50. ListItem() {
    51. Text(`template_2: ${item.item}`)
    52. .backgroundColor('#f1f3f5')
    53. .margin({
    54. left: 16,
    55. right: 16
    56. });
    57. };
    58. }, { cachedCount: 2 });
    59. }
    60. .layoutWeight(1)
    61. .width('100%');
    62. };
    63. }
    64. }
在 FAQ 中进行搜索
请输入您想要搜索的关键词