移动开发UI组件【免费下载链接】lithoA declarative framework for building efficient UIs on Android.项目地址https://gitcode.com/gh_mirrors/li/litho点击查看免费下载导读在 Litho 中组件树Component Tree上的每个节点都通过一个唯一标识——组件键key——来确立身份。Litho 依赖 key 在两次布局之间追踪组件身份并在状态更新时准确找到目标组件、为其写入新的状态值。本文以 keys-and-identity.md 为主线结合 litho-core 的底层实现与 sample 中的完整示例系统讲解 key 的自动生成规则、动态层级下自动 key 失效的原因以及如何在 Kotlin API 与 Spec API 中手动设置稳定 key帮助你在列表增删、条件渲染等场景中避免状态错位与 UI 异常。一、什么是组件 key组件树中的身份 IDKey 帮助 Litho 为组件树中代表某个节点的组件设置唯一身份。Litho 使用 key 达成两个核心目标在布局变化之间保持组件身份可追踪——同一个组件节点在两次渲染中是否仍是同一个组件由 key 判定正确识别状态更新的目标——当调用一次状态更新state update时Litho 需要依据 key 在遍历组件树的过程中找到对应节点并把新的状态值写入正确的位置。从源码实现看key 是全局的、跨层级的。以状态存储为例StateId.kt 定义的状态标识由treeId globalKey index三元组构成其中globalKey正是从组件树上计算出的全局键可见 key 直接参与状态值的定位与检索。默认情况下Litho 会基于组件类型 父组件 key自动为每个组件生成 key。但在实际 UI 中我们常常需要动态增删组件、调整兄弟节点顺序或按条件渲染某些组件此时自动生成的 key 可能无法满足跨更新稳定的要求。本页后续内容将解释自动 key 的生成机制以及何时、如何手动指定 key。一个需要牢记的前提只要组件渲染在组件树的同一节点上它就会被分配相同的 key。如果该节点改变了位置例如被移动到另一个父节点下或因为其他兄弟节点被删除/插入而改变了自身的位置那么它的 key 在两次 UI 更新之间不保证一致。注意这一点非常关键——Litho 在状态更新时会用 key 来判断该更新哪个组件并在遍历树、写入新状态值时靠 key 正确识别组件。key 一旦漂移状态就会认错门。二、自动生成 key类型 父 key 去重序号Litho 根据组件的类型以及它相对父组件的位置来生成 key如下图所示的基础组件树2.1 key 的拼接构成一个组件的 key 由以下三部分拼接而成组成部分含义父组件的 keyParents key当该组件是某个父组件的子节点时父组件的全局 key 会作为前缀组件自身的 keyComponents key由组件类型决定例如Row、Text这类类型标识去重 IDDeduplication ID该组件在同类型兄弟组件之间的位置序号下图展示了同一个组件树在加上 key 之后的样子为降低意外碰撞key collision的概率实际 key 计算中还包含了其他分隔符上图出于简化目的未展示。2.2 源码级的 key 生成实现自动 key 的生成逻辑集中在 ComponentKeyUtils.kt 中核心方法是generateGlobalKey(parentContext, parentComponent, childComponent)其流程可以归纳为判断是否手动 key通过childComponent.hasManualKey()判断若为手动 key则 key 以$前缀标记PREFIX_FOR_MANUAL_KEY $拼接父链若存在父组件则调用getKeyWithSeparator(parentGlobalKey, key)以,作为分隔符把父 key 与子 key 连接形成层级链同类型去重对于没有手动 key 的子组件调用getChildCountAndIncrement(childComponent)按类型计数再通过getKeyForChildPosition(currentKey, index)在序号非 0 时以!index后缀追加去重 ID。因此自动 key 的形态大致是父key,类型key!序号的层级拼接这与文档中父 key 组件 key 去重 ID的简化描述一一对应。2.3 为什么自动 key 不稳定在 ComponentTree 中检测到 key 碰撞时——典型场景是父组件创建了多个同类型的子组件——Litho 会根据它们的排列顺序为这些兄弟组件分配唯一 key。这带来的直接后果是自动生成的 key 不随组件移动而保持稳定。三、典型问题删除一个子组件key 发生漂移以文档中的组件树为例父组件下并排渲染了两个Row子组件各自带有一个有状态的组件。初始时第一个Row的 key 为...Row!0序号 0第二个Row的 key 为...Row!1序号 1。当第一个Row被移除后组件树变成下图更新之后初始树中的第二个 Row 变成了第一个类型为 Row 的子节点因此它的 key 从...Row!1变成了...Row!0——key 变了这会带来两重后果原状态丢失Litho 此前把该 Row 的状态映射在旧 key 上key 变化后旧 key 对应的状态值全部被重置UI 回到初始状态状态错位更严重的是旧 key 关联的状态会落到新获得该 key 的下一个 Row 组件头上即状态串台。可以想象这会引发令人头疼的 UI 缺陷计数值、开关状态、输入内容等莫名跳变到另一个组件上。一句话总结Litho 的自动 key 生成是尽力而为best-effort的在运行时实现下无法做到完全确定性deterministic——因为去重 ID 依赖运行时兄弟节点的排列与增删情况。四、手动指定 key稳定身份的解法对于组件位置可能动态变化的 UI 层级必须在组件上手动指定跨 UI 更新保持稳定的 key。手动 key 会始终优先于自动生成的 key从 generateGlobalKey 中if (hasManualKey) $${childComponent.key}的分支可见手动 key 直接使用用户提供的值不再走类型计数去重。4.1 Kotlin API内置全局key()方法在 Kotlin API 中可以通过内置的全局key()方法为组件设置手动 key完整示例见 IdentityRootComponent.ktclass IdentityRootComponent : KComponent() { override fun ComponentScope.render(): Component { val isFirstCounterEnabled useState { true } val isSecondCounterEnabled useState { true } return Column(style Style.onVisible { ... }) { if (isFirstCounterEnabled.value) { child( key(first_row) { Row { child(CounterComponent()) child( Text( text X, textSize 30.dp, style Style.margin(all 30.dp).onClick { isFirstCounterEnabled.update(false) })) } }) } if (isSecondCounterEnabled.value) { child( key(second_row) { Row { child(CounterComponent()) child( Text( text X, textSize 30.dp, style Style.margin(all 30.dp).onClick { isSecondCounterEnabled.update(false) })) } }) } } } }这里的CounterComponent是一个持有计数值状态useState { 0 }的组件完整定义见 CounterComponent.kt。两个 Row 分别被手动 key 为first_row与second_row点击X时对应行的isFirstCounterEnabled/isSecondCounterEnabled被置为false该 Row 从树中被移除由于手动 key 不参与同类型去重剩余 Row 的 key 始终保持不变其内部的计数器状态不会丢失或串台。key()的底层实现位于 KComponent.kt它是一个内联函数先执行组件 lambda 拿到组件实例再调用setKeyForComponentInternal(component, key)把手动 key 写入该组件。源码注释给出了典型用法key(my_key) { Text(...) }4.2 Spec API通用key组件属性在基于注解的 Spec APIJava中可以使用通用的key组件属性在创建组件时手动设置 key完整示例见 IdentityRootComponentSpec.javaLayoutSpec class IdentityRootComponentSpec { OnCreateInitialState static void onCreateInitialState( ComponentContext c, StateValueBoolean isFirstCounterEnabled, StateValueBoolean isSecondCounterEnabled) { isFirstCounterEnabled.set(true); isSecondCounterEnabled.set(true); } OnCreateLayout static Component onCreateLayout( ComponentContext c, State boolean isFirstCounterEnabled, State boolean isSecondCounterEnabled) { return Column.create(c) .child( isFirstCounterEnabled ? Row.create(c) .key(first_row) .child(CounterComponent.create(c)) .child( Text.create(c) .text(X) .paddingPx(YogaEdge.START, 16) .clickHandler(IdentityRootComponent.onClickRemoveFirstChild(c))) .build() : null) .child( isSecondCounterEnabled ? Row.create(c) .key(second_row) .child(CounterComponent.create(c)) .child( Text.create(c) .text(X) .paddingPx(YogaEdge.START, 16) .clickHandler(IdentityRootComponent.onClickRemoveSecondChild(c))) .build() : null) .build(); } OnEvent(ClickEvent.class) static void onClickRemoveFirstChild(ComponentContext c) { IdentityRootComponent.onRemoveFirstChild(c); } OnEvent(ClickEvent.class) static void onClickRemoveSecondChild(ComponentContext c) { IdentityRootComponent.onRemoveSecondChild(c); } OnUpdateState static void onRemoveFirstChild(StateValueBoolean isFirstCounterEnabled) { isFirstCounterEnabled.set(false); } OnUpdateState static void onRemoveSecondChild(StateValueBoolean isSecondCounterEnabled) { isSecondCounterEnabled.set(false); } }同样地通过.key(first_row)与.key(second_row)为两个 Row 指定了稳定身份。Builder 上的key(Nullable String key)方法在 Component.java 中实现它会拒绝 null key 并给出明确的报错提示。4.3 手动 key 的注意事项从 ComponentKeyUtils.generateGlobalKey 的实现可以看到手动 key 同样会经过父 key 拼接和同 key 去重两个环节手动 key 会拼接在父组件全局 key 之后形成父key,$子key的形式SEPARATOR_FOR_MANUAL_KEY ,$因此手动 key 只需要在兄弟节点之间保持唯一无需刻意全局唯一若同一父组件下出现重复的手动 keyLitho 会通过getManualKeyUsagesCountAndIncrement(key)检测并对重复 key 追加序号使其唯一同时发出ComponentKeyUtils:DuplicateManualKey警告见 logDuplicateManualKeyWarning。重复 key 被改写会导致行为不符合预期因此务必保证手动 key 在兄弟间不重复。五、进阶技巧用 key 强制重置组件状态除了维持身份稳定手动 key 还有一个巧妙的用途强制组件状态基于某些 props 重新初始化。当手动 key 是某些 props 的函数时只要这些 props 发生变化key 就会随之变化由于 Litho 用 key 定位状态key 一变旧状态便不再与之匹配组件就会被当作新组件处理状态值自然回到初始值。这一模式非常适合以下场景同一组件复用在不同数据上例如一个详情卡片组件当展示的 item id 变化时需要清空内部编辑状态、滚动位置等条件性重置希望某个标志位从false变回true时组件重新走初始化逻辑key 作为 props 的函数key(detail_${item.id}) { DetailCard(item item) }item 变化即状态重置。提示这是手动 key 的额外红利——用 key 表达身份与数据版本的双重语义让状态生命周期与数据生命周期对齐。六、总结与实践建议回到核心结论可以总结为一张决策表场景自动 key 是否够用建议静态层级节点位置从不变化是无需手动 key同类型兄弟组件被插入/删除节点相对位置变化否为可能移动的节点设置手动 key组件被条件性渲染/移除否为条件分支内的根节点设置手动 key希望 props 变化时重置组件状态视需求让手动 key 成为 props 的函数手动设置 key 的要点回顾Kotlin API使用全局内联函数key(xxx) { ... }见 KComponent.ktSpec API使用 Builder 的.key(xxx)属性见 Component.java手动 key优先于自动 key生成规则与自动 key 相同父 key 拼接 同 key 去重见 ComponentKeyUtils.kt兄弟节点之间避免重复 key否则会触发DuplicateManualKey警告并被改写需要重置状态的场景可让 key 成为相关 props 的函数。完整的可运行示例位于 sample 工程中Kotlin 版本 IdentityRootComponent.kt、Java 版本 IdentityRootComponentSpec.java 及配套的 CounterComponent.kt。想进一步理解 key 在状态协调中的作用可继续阅读同目录下的 communicating-between-components.md、hoisting-state.md 与 componenttree.md。赞分享移动开发UI组件【免费下载链接】lithoA declarative framework for building efficient UIs on Android.项目地址https://gitcode.com/gh_mirrors/li/litho点击查看免费下载相关推荐Litho中的组件标识key属性使用最佳实践Litho中的组件标识key属性使用最佳实践 在Android应用开发中高效的UI渲染和状态管理是提升用户体验的关键。Litho作为一款声明式UI框架通过移动开发UI组件Apache APISIX key-auth 插件实战基于 API Key 的消费者身份认证与限流Apache APISIX key auth 插件实战基于 API Key 的消费者身份认证与限流 导读 key auth 是 Apache APISIX 中API网关后端云原生微服务ahooks useDynamicList 深度指南动态列表管理与唯一 Key 生成原理ahooks useDynamicList 深度指南动态列表管理与唯一 Key 生成原理 useDynamicList 是 ahooks 中用于管理动态列表状前端上一篇CANN/GE SO in OM 特性说明下一篇Refine v5 shadcn/ui RefreshButton 详解基于 useInvalidate 的详情页数据就地刷新创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
AI编程:告别重复造轮子,用TaoToken统一Key打通Codex配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 21:25:04
CSS Modules 完全指南:用 Lightning CSS 实现局部作用域样式 前端开发工具 【免费下载链接】lightningcss An extremely fast CSS parser, transformer, bundler, and minifier written in Rust. 项目地址: https://gitcode.com/gh_mirrors/li/lightningcss 点击查看 免费下载 CSS 默认是全局命名空间:不同文件里同… · 2026/9/27 21:25:04
Nature子刊 | 皮层间配对关联刺激(ccPAS)在脑网络中的应用 该研究首次将双位点ccPAS用于边缘型人格障碍患者,靶向右侧IFC-pre-SMA抑制控制环路,以4ms与100ms ISI对照检验通路特异性调控。结果发现两组停止信号反应时均缩短,但无组间差异,提示效应可能非通路特异。(🔗… · 2026/9/27 22:03:01
早期项目怎么找天使投资人?没有熟人资源也能跑通的路径 核心结论:早期项目怎么找天使投资人?常见路径包括结构化匹配服务、线下路演与行业网络、专业 FA 机构等。没有熟人资源的项目方,可以先通过公开渠道、行业网络或结构化匹配服务建立目标名单;进入材料和交易需求较明确的阶段后&… · 2026/9/27 22:02:55
数据质量成本(COPQ)怎么算:把 15%~20% 的营收损失拆成四类可核账成本 数据质量成本(COPQ)怎么算:把 15%~20% 的营收损失拆成四类可核账成本
标签:#数据质量 #数据治理 #成本管理 #数据管理 #数据资产 摘要: 行业口径普遍引用"数据质量问题每年造成企业 15%~20% 的营收损失"“7… · 2026/9/27 22:02:55
让建站公司做网站需要什么?3步教你避开域名服务器坑 让建站公司做网站需要什么?3步教你避开域名服务器坑 域名服务器搞不懂,是90%老板找建站公司时的第一道坎。很多人以为只要给个名字就能开工,结果在服务器配置和域名解析上卡了半个月。其实,让建站公司做网站,核心在于 怎么选… · 2026/9/27 22:02:42
如何制作一个网页网站:被黑挂马后,我花了3万重做 如何制作一个网页网站:被黑挂马后,我花了3万重做 网站被黑挂马不知道怎么办?这是上周深夜我接到客户电话时,听到的第一句话。他的电商首页突然弹出一堆博彩广告,浏览器提示不安全,后台登录也进不去了。他问我:“这种烂摊子,彻底修好大概要多少钱?”… · 2026/9/27 22:02:30
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01