- 设计系统
- 前端
- 开发工具
- UI组件
【免费下载链接】Lona
A tool for defining design systems and using them to generate cross-platform UI code, Sketch files, and other artifacts.
本篇指南围绕 Lona 官方性能文档 studio/docs/performance.md 展开,系统讲解如何让 Lona 生成的前端组件(尤其是 Swift/UIKit、AppKit 平台)运行得更快、代码更精简。你将掌握三大层面的优化手段:在 Lona Studio 中通过"渲染更少"与合理切分组件边界来从源头减负;在 Swift 生成代码中通过批量设置Parameters、利用函数参数代理机制来减少update()调用;以及在处理表格与集合、压缩生成代码体积时应该遵循的具体取舍原则。文中所有结论均对照仓库内编译器源码与生成示例,可直接落地到你自己的 Lona 工作区中。
一、性能优化的第一站:Lona Studio
Lona 的性能优化有一条清晰的优先级路径:任何能在设计阶段(Lona Studio)完成的优化,成本最低、收益最广——它会影响后续生成的所有平台(Swift、JavaScript 等)的输出。因此,动手写任何平台级优化代码之前,先回到.component文件本身。
1.1 渲染更少(Render Less)
提升性能最有效的手段是"少渲染"。在设计组件时,尽量用最少、最必要的图层数来完成 UI:
- 检查多余的包装
View:如果一个视图仅用于布局,且只有一个子视图,那么它很可能可以被移除。此时可以把它身上的 padding 直接改写成子视图的 margin,从而减少一层真实视图。 - 删除永久隐藏的图层:如果一个图层在任何参数组合下都永远不会可见,直接删除即可。这类图层虽然不可见,但依然会被生成进代码中,可能影响生成 UI 的初始化和布局性能。
这条原则对应到生成代码的规模上是立竿见影的——每一层View在 Swift 中都对应一个UIView/NSView实例及其约束集合,层数越少,运行时对象越少。
1.2 改善组件边界(Component Boundaries)
Lona 生成的组件遵循"参数变化才重渲染"的模型:只有参数发生变化时,组件才会重新执行其逻辑并刷新 UI。基于这个模型,可以围绕组件边界做两项优化:
- 拆分巨型组件:如果一个单体组件拥有大量图层,修改它任何一个参数都会触发整棵视图树的重渲染,成本较高。如果其中有一小块 UI 很少变化,就把它抽成独立的子组件,由大组件把相关参数传递给它。这样大部分参数变化只会触发小块区域的重渲染。
- 利用列表/集合中的"实例化与配置分离":当组件被用在列表或集合视图(如
UICollectionView、UITableView)中时,组件的实例化与参数配置通常发生在不同时刻。如果你的 UI 中存在一种特别常见的配置状态,可以考虑把它做成组件的初始状态,这样大部分布局工作会在实例化阶段一次性完成,后续滚动复用单元时只需做少量增量配置。
二、Swift 平台优化
在 Lona 生成的 Swift 代码里,最根本的性能杠杆依然是减少UIView的数量——也就是回到 Lona Studio 中减少图层数。假设你已经在设计阶段完成了图层精简,Swift 侧还有以下几类可以继续挖掘的优化点。
2.1 Parameters:批量设置,避免重复执行 update()
Lona 为每个组件生成一个Parameters结构体,组件对外暴露的每个参数都会封装成该结构体的字段,同时组件内部维护一个公开的parameters成员变量。任何一次参数变更都会触发update()——它负责运行组件逻辑、依据参数刷新 UI。
从编译器源码可以看清这条调用链:在 swiftComponent.re 中,parameters变量被声明为带didSet的存储属性,didSet中比较新旧值,一旦不相等就调用update():
var parameters: Parameters { didSet { if parameters != oldValue { update() } } }而update()本身被生成为private方法(见 swiftComponent.re),内部包含依据参数刷新所有图层属性、可见性、矢量图层重绘等逻辑。
由此得出第一条 Swift 侧优化原则:
逐个设置参数会让
update()执行多次;应当一次性构造/修改一个完整的Parameters结构体,再整体赋值给组件的parameters属性,让didSet只触发一次刷新。
例如,比起下面这种写法:
component.title = "Hello" component.subtitle = "World" component.imageName = "avatar"更推荐的做法是:
var params = component.parameters params.title = "Hello" params.subtitle = "World" params.imageName = "avatar" component.parameters = params注意:Parameters声明为Equatable,但这一能力对函数类型参数是"名不副实"的——Swift 本身不支持函数做相等比较。因此 Lona 在处理函数参数的 setter 时走了另一条路(详见下文 2.2)。官方文档也提到,后续可能移除Equatable的一致性,改为提供一个专门比较两个Parameters非函数内容的辅助方法。
2.2 函数参数代理:变更函数参数不会触发 update()
Lona 在函数参数与逻辑之间引入了一层间接代理,从而保证组件始终调用到最新传入的函数,同时又不必重跑组件逻辑。
编译器注释给出了这一设计动机(见 swiftComponent.re):由于 Swift 不支持函数相等比较,目前也无法判断"可选函数是否为 nil",所以对于函数类型参数,Lona 生成了一个handle...形式的私有代理方法,它直接调用参数中保存的最新函数引用(见 swiftComponent.re),完全不经过update()。
这带来的性能结论是:
修改一个函数参数永远不会调用
update()——因为在绝大多数场景下,函数本身不会影响 UI 布局与外观,只是"动作回调"。这避免了仅因为换了一个回调就让整棵视图树重新刷新的浪费。
官方文档同时指出:未来会提供一个"退出该优化"的开关,让开发者在个别边缘场景下(例如函数变化确实需要影响 UI 时)选择让参数变化重新触发update()——该能力目前尚未实现,需留意版本演进。
2.3 表格与集合(Tables & Collections)
Lona 生成的组件在设计上就是为表格和集合视图(UICollectionView/UITableView场景)准备的,如果在你自己的使用中表现不佳,通常需要回到 2.1 的参数批量设置与 1.2 的实例化/配置分离上找原因。
关于如何让集合单元获得灵敏的 tap/highlight 反馈状态,官方在专门指南 collections.md 中有详细说明,可以按需查阅。仓库中也提供了配套的集合视图辅助实现,例如生成目录下的 LonaCollectionView.swift。
2.4 代码体积:条件约束的组合爆炸(N² 问题)
一般情况下,生成代码的体积不值得过分担心——因为这些组件本就不该被手工修改。但在两种情况下你需要关注代码量:
- 你希望把某个文件移出生成目录、手工修改其中一部分;
- 生成代码的体积确实膨胀到了难以维护的程度。
此时有两类手段:
手段一:拆分大组件。把大组件拆成多个小组件不会减少代码总量,但会让代码分布在更可控的单元里,更易维护。
手段二:减少条件约束(conditional constraints)的数量。这是代码体积问题的核心来源。当组件根据参数隐藏/显示某个视图时,Lona 会生成一组"仅在该视图可见时才激活"的约束,并为每个可见性组合生成一套约束激活方案,运行时根据当前哪些视图可见来决定启用哪一套。
从编译器实现可以印证这一点:在 swiftConstraint.re 中,生成逻辑为每一种"视图隐藏组合"生成一个switch分支;随后生成的私有函数conditionalConstraints(...)以每个"可能隐藏的视图"的isHidden布尔值作为参数(见 swiftConstraint.re),内部针对每个布尔组合返回对应的约束数组。生成的 Swift 代码大致长这样:
private func conditionalConstraints(titleViewIsHidden: Bool) -> [NSLayoutConstraint] { var constraints: [NSLayoutConstraint?] = [] switch (titleViewIsHidden) { case (true): constraints = [ bottomViewTopAnchorInnerViewBottomAnchorConstraint, ] case (false): constraints = [ titleViewTopAnchorInnerViewBottomAnchorConstraint, ] } return constraints.compactMap({ $0 }) }每个可能隐藏的视图都会给这个函数增加一个布尔维度,因此生成的代码量与"有时隐藏的视图数量 N"呈 N² 关系。
对应的优化策略:
- 不要隐藏一大堆相互独立的视图,而是用一个包装视图(wrapper view)包住它们,只对包装视图做显隐切换。这样 N 大幅下降,生成代码显著减少;
- 但包装视图会带来渲染性能损耗,因此要谨慎使用,仅在代码体积确实成为问题时采用;
- 把大组件拆分成小组件同样能缓解这个问题(N 的基数变小)。
2.5 组件初始化链路与实例化成本
为了理解"初始状态优化"为何有效,值得看一眼组件初始化时发生了什么。Lona 生成的init(parameters:)大致流程为(见 swiftComponent.re):
- 把传入的
Parameters赋值给self.parameters; - 调用
super.init(frame: .zero); - 调用
setUpViews()构建视图层级; - 调用
setUpConstraints()建立约束; - 调用一次
update()完成首次刷新。
也就是说,实例化阶段天然会完成"一次完整配置"的工作。如果你把最常见的参数配置做成初始状态(Parameters的默认值),滚动复用时就可以避免在cellForItemAt之类的回调里做大量差异化配置,从而让"实例化时做重活、复用时做轻活"。
三、JavaScript 平台:规划中
官方文档明确指出,JavaScript 平台的优化指南状态为"Coming soon..."(尚未发布)。也就是说,截至当前仓库版本,Lona 的官方性能文档只覆盖了 Lona Studio 设计与 Swift 两大块,JavaScript(含 React DOM、React Native 等目标)暂时没有成文的专项优化说明。
对于 JavaScript 目标,现阶段可以直接套用本文第一部分的通用原则——在 Lona Studio 里精简图层、合理切分组件——这些优化会传导到所有平台的生成结果上。仓库的生成示例(如 examples/generated/test/react-dom 与 examples/generated/test/react-native)可以帮你观察同一组件在不同平台下的代码形态,便于评估图层精简的实际收益。
四、优化路线图小结
| 优化层次 | 核心手段 | 收益 |
|---|---|---|
| Lona Studio(全平台通用) | 移除仅用于布局且只有单子的包装 View、删除永久隐藏图层 | 减少所有平台的生成视图数量 |
| Lona Studio(全平台通用) | 拆分巨型组件、把常见配置设为初始状态 | 缩小每次参数变化的重渲染范围、优化列表复用 |
| Swift | 批量赋值Parameters,而非逐参数设置 | 避免update()被多次触发 |
| Swift | 利用函数参数代理机制 | 更换回调不触发update() |
| Swift | 用包装视图收敛"有时隐藏"的视图、拆分子组件 | 打破条件约束的 N² 代码膨胀 |
| JavaScript | 暂无官方专项指南(Coming soon) | 先复用 Studio 侧优化 |
一句话总结:先在设计器里"少画",再在 Swift 侧"少更新、少膨胀"——前者是所有平台性能的地基,后者是 UIKit/AppKit 场景下最值得关注的两条增量优化路径。
备注:本文描述的性能特性均基于当前仓库版本。
update()的触发时机、函数参数代理行为、条件约束生成策略等细节可在 swiftComponent.re 与 swiftConstraint.re 中直接查看源码确认,若升级 Lona 版本请以新版生成代码为准。
- 设计系统
- 前端
- 开发工具
- UI组件
【免费下载链接】Lona
A tool for defining design systems and using them to generate cross-platform UI code, Sketch files, and other artifacts.
相关推荐
Lona 实践指南:在 UICollectionView 中集成与调优 Lona 生成的交互式组件
Lona 实践指南:在 UICollectionView 中集成与调优 Lona 生成的交互式组件 本文面向使用 Lona https://link.gitco
设计系统前端开发工具UI组件SwiftDate性能调优指南:从启动时间到运行时优化
SwiftDate性能调优指南:从启动时间到运行时优化 你是否遇到过SwiftDate初始化缓慢、日期处理卡顿的问题?本文将从启动时间优化到运行时效率提升,全面
移动开发Rust-esp32-std-demo项目架构解析:深入理解esp-idf-sys、esp-idf-hal和esp-idf-svc
Rust esp32 std demo项目架构解析:深入理解esp idf sys、esp idf hal和esp idf svc Rust esp32 std
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考