联动编辑如何守住总量:不变量、局部补偿与整数精度
以多字段总量固定为唯一问题,讨论状态建模、相邻补偿、无效输入和整数余量。尺寸编辑只是案例,方法也适用于配额分配与布局编辑。
- TypeScript
- 状态建模
- 不变量
- 数值精度
关联案例:定制家具设计与报价系统。本文只讨论“多个值联动时如何保持总量与边界”,不展开行业尺寸规则,也不讨论 Canvas 绘制。
多个输入框,其实共同编辑一份状态
布局宽度、配额分配、固定总数的任务分组,都可能遇到同一个问题:用户改动其中一项时,总量不能随之改变,其他项又不能突破上下限。
如果每个输入框只验证自己,所有值都合法也不代表组合合法。例如每项都大于零,但相加已经超过总量。
因此,首先要明确整体不变量:
可分配总量 S = x₁ + x₂ + … + xₙ
每个 xᵢ ≥ 下限 L
还需要确定变化由谁承担。等比例缩放、相邻补偿和逐项分摊都可能合理,但它们会给用户完全不同的交互感受。技术实现应接收已确定的规则,而不是在 watcher 中临时决定。
单一状态入口,避免互相触发的 watcher
直接监听 A 更新 B,再监听 B 更新 A,很容易形成重复计算、循环更新或中间非法状态。
更清楚的路径是:输入产生一次编辑意图,纯函数计算完整的新状态,校验通过后一次性替换原状态。表单展示依赖这个结果,不再各自维护独立真相。
编辑意图 → 约束计算 → 合法的新数组 → 一次提交
↘ 无效:保留旧状态并反馈
用相邻补偿实现局部变化
下面选择一种明确的示例策略:普通项由右邻项补偿,最后一项由左邻项补偿。输入与输出使用整数最小单位,函数不修改原数组。这里的默认下限为零,业务可以传入其他值。
export function redistribute(
values: readonly number[],
index: number,
target: number,
minimum = 0
): number[] {
if (!Number.isSafeInteger(minimum) || minimum < 0
|| values.length < 2 || !Number.isInteger(index)
|| index < 0 || index >= values.length) {
throw new RangeError('无效的约束或联动位置')
}
if (!values.every(v => Number.isSafeInteger(v) && v >= minimum)
|| !Number.isSafeInteger(target) || target < minimum) {
throw new RangeError('分配值必须是满足下限的整数')
}
const neighbor = index === values.length - 1
? index - 1 : index + 1
const delta = target - values[index]
const compensation = values[neighbor] - delta
if (!Number.isSafeInteger(compensation) || compensation < minimum) {
throw new RangeError('相邻项无法承担本次变化')
}
const next = [...values]
next[index] = target
next[neighbor] = compensation
return next
}
当目标增加 Δ、相邻项减少 Δ 时,合计保持不变;其他项也不受影响。若补偿项不满足下限,就拒绝整次变更,不留下“当前项已改、邻项还没改”的半成品。
示例只实现下限约束和邻项补偿。上限、锁定项、多邻项分摊和一项分配都应另行定义,不应默认套用这个函数。
精度问题要在建模时解决
假设要把 2366 个单位分给三项。先向下取整,每项 788,余量是 2;再按稳定顺序分配余量,得到 [789, 789, 788]。分别四舍五入会得到 2367,破坏总量。
在尺寸案例中,2438mm 外宽先扣除四块 18mm 固定竖板,得到 2366mm 可分配净宽。固定占用与可编辑分配必须是两个概念。将中间格从 789 改为 820、下限传入 400,会得到 [789, 820, 757]。
这里采用整数毫米。对于其他场景,应先定义最小单位与取整策略;不要把这个例子的单位与下限当成通用规则。加入或删除分配项时,固定占用也可能改变,需要重算可分配总量。
围绕不变量设计测试
- 合法变更后,总量不变;计算前后只有目标项和补偿项变化。
- 原数组不变,最后一项能够正确向左补偿。
- 小于下限、非整数、无效索引与补偿不足都被拒绝。
- 整数均分后总和严格等于原始可分配量,余量分配顺序可复现。
- 界面层只在计算成功后提交状态,失败时显示用户可以理解的限制原因。
这篇文章要复用的是“把整体约束放到单一状态变换中”的方法。具体怎样扣除固定占用、选哪一个补偿对象,属于应用场景自己的规则。