案例解析关于 ArkUI 框架中 ForEach 的潜在陷阱与性能优化
本文分享自华为云社区《深入解析ForEach的潜在陷阱与性能优化:错误用法与性能下降的案例分析》,作者:柠檬味拥抱 。
在 ArkUI 框架中,ForEach 接口是基于数组类型数据进行循环渲染的强大工具。它需要与容器组件搭配使用,并能够根据数据源动态生成相应的子组件。以下是对 ForEach 接口的详细解析,包括接口描述、参数说明、键值生成规则以及使用场景的示例。
ForEach 接口概述
介绍
ForEach 接口基于数组类型数据进行循环渲染,需要与容器组件配合使用。它能够根据数据源的变化,动态生成对应的子组件,并将其渲染到界面上。
接口描述
参数说明
arr: 数据源,为 Array 类型的数组。可以为空数组,但不能改变包括数组本身在内的任何状态变量。
itemGenerator: 组件生成函数,为数组中的每个元素创建对应的组件。必须是 ForEach 父容器所允许的子组件类型。
keyGenerator: 键值生成函数,为数据源的每个数组项生成唯一且持久的键值。可选参数,如果未定义,将使用默认的键值生成函数。
键值生成规则
在 ForEach 循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。键值的生成规则由开发者定义,可以使用默认规则或自定义规则。
默认规则
系统对于键值的生成判断主要与 itemGenerator 函数的第二个参数 index 以及 keyGenerator 函数的返回值有关。具体的键值生成规则判断逻辑可以参考文档中的图 1 ForEach 键值生成规则。
组件创建规则
在确定键值生成规则后,ForEach 的 itemGenerator 函数会根据键值生成规则为数据源的每个数组项创建组件。组件的创建包括两种情况:ForEach 首次渲染和 ForEach 非首次渲染。
首次渲染
在首次渲染时,根据键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。以下是一个示例代码:
在上述代码中,根据键值生成规则为数据源 simpleList 的每个数组项生成唯一键值,并创建对应的 ChildItem 组件渲染到界面上。
非首次渲染
在非首次渲染时,ForEach 会检查新生成的键值是否在上次渲染中已经存在。如果键值不存在,则会创建一个新的组件;如果键值存在,则不会创建新的组件,而是直接渲染该键值所对应的组件。以下是一个示例代码:
在上述代码中,当渲染到索引为 2 的’two’时,因为该键值已存在于上次渲染中,所以不会创建新的 ChildItem 组件,而是直接复用已有的组件。
使用场景
ForEach 组件在开发过程中的主要应用场景包括:
数据源不变: 数据源可以直接采用基本数据类型数组,在页面加载状态时使用骨架屏列表进行渲染展示。
数据源数组项发生变化: 在数据源数组项发生变化的场景下,例如进行数组插入、删除操作或者数组项索引位置发生交换时,数据源应为对象数组类型,并使用对象的唯一 ID 作为最终键值。
数据源数组项子属性变化: 当数据源的数组项为对象数据类型,并且只修改某个数组项的属性值时,需要结合 @Observed 和 @ObjectLink 装饰器使用,以实现 ForEach 的重新渲染。
以下是一个示例代码,演示了上述场景的应用:
在该示例中,通过对 Article 类使用 @Observed 和 @ObjectLink 装饰器,实现了数据源数组项子属性变化时的重新渲染。当点击文章卡片上的点赞按钮时,修改了数组项的点赞状态和点赞数量,触发了 ForEach 的重新渲染。
高级用法
条件渲染逻辑
ForEach 的 itemGenerator 函数支持包含 if/else 条件渲染逻辑。这意味着你可以根据数据源的不同条件,动态生成不同类型的子组件。以下是一个示例代码:
在上述示例中,根据 condition 的值,动态选择渲染 ChildItem1 或 ChildItem2。这为根据业务逻辑灵活地调整渲染行为提供了便利。
LazyForEach 的性能优化
如果数据源非常庞大或者有特定的性能需求,推荐使用 LazyForEach 组件进行懒加载。LazyForEach 在初始化渲染时不会一次性加载所有数据,而是根据滚动位置和用户操作,动态地加载可见区域的数据项。这有助于提高页面的渲染性能。以下是一个简单的示例代码:
在上述示例中,LazyForEach 会根据用户的滚动位置,只加载可见区域的数据项,从而在处理大型数据源时提升性能。
渲染结果预期
在使用 ForEach 接口时,需要注意渲染结果的预期。在 UI 更新的场景下,如果出现重复的键值,框架可能无法正常工作。因此,在使用自定义键值生成函数时,需要确保生成的键值具有唯一性,避免出现渲染结果不符合预期的情况。
ForEach 的错误使用案例与性能降低
在使用 ForEach 过程中,对于键值生成规则的理解不够充分可能导致错误的使用方式,带来功能和性能方面的问题。以下是一个示例,展示了错误使用 ForEach 可能导致的渲染结果非预期和性能降低的情况。
渲染结果非预期
在下面的示例中,通过设置 ForEach 的第三个参数 KeyGenerator 函数,自定义键值生成规则为数据源的索引 index 的字符串类型值。当点击父组件 Parent 中的“在第 1 项后插入新项”文本组件后,界面会出现非预期的结果。
在上述代码中,初始渲染效果和点击“在第 1 项后插入新项”后的渲染效果如下图所示:
初始渲染时,创建的键值依次为 “0”、“1”、“2”。插入新项后,数据源 simpleList 变为 ['one', 'new item', 'two', 'three']。框架监听到 @State 装饰的数据源长度变化触发 ForEach 重新渲染。ForEach 遍历新数据源,遍历数据项 “one” 时生成键值 “0”,存在相同键值,因此不创建新组件。继续遍历数据项 “new item” 时生成键值 “1”,存在相同键值,因此不创建新组件。继续遍历数据项 “two” 生成键值 “2”,存在相同键值,因此不创建新组件。最后遍历数据项 “three” 时生成键值 “3”,不存在相同键值,创建内容为 “three” 的新组件并渲染。
从以上可以看出,最终键值生成规则包含 index 时,期望的界面渲染结果为 ['one', 'new item', 'two', 'three'],而实际的渲染结果为 ['one', 'two', 'three', 'three'],渲染结果不符合开发者预期。因此,开发者在使用 ForEach 时应尽量避免最终键值生成规则中包含 index。
渲染性能降低
在下面的示例中,ForEach 的第三个参数 KeyGenerator 函数处于缺省状态。根据上述键值生成规则,此例使用框架默认的键值生成规则,即最终键值为字符串 index + '__' + JSON.stringify(item)。当点击“在第 1 项后插入新项”文本组件后,ForEach 将需要为第 2 个数组项以及其后的所有项重新创建组件。
在上述代码中,初始渲染效果和点击“在第 1 项后插入新项”后的渲染效果如下图所示:
点击“在第 1 项后插入新项”文本组件后,IDE 的日志打印结果如下所示:
插入新项后,ForEach 为 “new item”、 “two”、 “three” 三个数组项创建了对应的组件 ChildItem,并执行了组件的 aboutToAppear() 生命周期函数。这是因为:
在 ForEach 首次渲染时,创建的键值依次为 0__one、1__two、2__three。
插入新项后,数据源 simpleList 变为 ['one', 'new item', 'two', 'three'],ArkUI 框架监听到 @State 装饰的数据源长度变化触发 ForEach 重新渲染。
ForEach 依次遍历新数据源,遍历数据项 “one” 时生成键值 0__one,键值已存在,因此不创建新组件。继续遍历数据项 “new item” 时生成键值 1__new item,不存在
相同键值,创建内容为 “new item” 的新组件并渲染。继续遍历数据项 “two” 生成键值 2__two,不存在相同键值,创建内容为 “two” 的新组件并渲染。最后遍历数据项 “three” 时生成键值 3__three,不存在相同键值,创建内容为 “three” 的新组件并渲染。
尽管此示例中界面渲染的结果符合预期,但每次插入一条新数组项时,ForEach 都会为从该数组项起后面的所有数组项全部重新创建组件。当数据源数据量较大或组件结构复杂时,由于组件无法得到复用,将导致性能体验不佳。因此,除非必要,否则不推荐将第三个参数 KeyGenerator 函数处于缺省状态,以及在键值生成规则中包含数据项索引 index。
结语
通过深入了解 ForEach 接口的使用方法和高级用法,我们可以更加灵活地处理不同场景下的数据渲染需求。在实际开发中,结合条件渲染逻辑和性能优化,能够提升用户体验并优化应用性能。希望本文对你理解和应用 ArkTS 中的 ForEach 接口提供了帮助。
ForEach 接口是 ArkUI 框架中用于循环渲染数组类型数据的重要工具,通过合理的键值生成规则和组件创建规则,能够应对各种数据变化情况。在开发过程中,根据具体场景选择合适的使用方式,可以使界面动态展示和用户交互更加灵活和高效。
版权声明: 本文为 InfoQ 作者【华为云开发者联盟】的原创文章。
原文链接:【http://xie.infoq.cn/article/e8a9d6dfdd3834bc0f15f4e44】。文章转载请联系作者。
评论