2.3 KiB
2.3 KiB
第六章:为什么只求高度比拿到每行文字便宜
6.1 计算结果越详细,需要的工作越多
假设一个段落有 50 行。高度只需要一个行数;返回每行文字则还要确定范围、切字符串、拼接内容并创建结果对象。这些临时对象最终还需要垃圾回收。
因此选择 API 时应先问:调用者实际要什么?
| 需求 | 准备方式 | 布局 API |
|---|---|---|
| 高度、行数 | prepare() |
layout() |
| 行数、最宽行 | prepare() |
measureLineStats() |
| 每行范围与宽度 | prepare() |
walkLineRanges() |
| 每行文字 | prepareWithSegments() |
layoutWithLines() |
| 逐行取文字,可按需停止 | prepareWithSegments() |
layoutNextLine() |
普通 prepare() 的最终 handle 不保留分段字符串;准备过程中仍会使用文字,不能因此说它完全没有字符串处理。
6.2 范围与文字分开生成
一行可以先表示为:
从第几个 segment 的第几个字素开始
到第几个 segment 的第几个字素结束
这一行有多宽
这是 range。需要显示那一行时,再把 range 变成实际字符串,这一步叫 materialize,即生成具体内容。
walkLineRanges() 不生成行字符串,但公共回调接收的 range 及其游标仍然会分配对象。它比“每行完整文字”做得少,也不应被描述成零分配。
line-text.ts还用 WeakMap 按 handle 缓存所需分段的字素结束位置。只有行边界切进一个分段内部、确实需要定位字素时,才会补上相关数据。
6.3 流式逐行适合什么情境
如果只显示前三行摘要,逐行 API 可以在取到所需行数时停止;如果每行可用宽度不同,也可以逐次传入不同宽度。
let cursor
for (let i = 0; i < 3; i++) {
const line = layoutNextLine(preparedWithSegments, 320, cursor)
if (!line) break
console.log(line.text)
cursor = line.end
}
这个片段假设已经导入 layoutNextLine 并准备好 preparedWithSegments。它演示逐行读取,不包含省略号处理。高度用途直接选 layout() 会更合适。
本章检查:虚拟列表只想知道 10,000 条消息各有多高,是否需要为每条创建所有行字符串?不需要。