前端如何友好的展现数值?本文基于实践总结了一些原则,介绍封装的工具库 Number Display ,并分析源码的实现。前端
Number Display 有适用于 Web 的 JavaScript 版和适用于 Flutter 的 Dart 版。git
JavaScript 版github
Dart 版web
在前端开发中,数值展现是一个常见的需求。不一样于统计或实验报表对精确性和规范性的注重,前端展现数值时更注重用户友好,让人一眼能感知数字,而且保持页面简洁、整齐。正则表达式
不管是移动端的 App ,仍是管理后台型的页面,或者流行的数据大屏,如下一些需求是展现数值时经常遇到的,具备必定的共性:数组
若是能有一个工具函数,只需简单的配置,即可将输入的数值转换成符合要求的字符串就行了,特别是当长度有限时,能自动经过变换单位,压缩小数位的方式确保字符串不溢出长度限制,好比:bash
-254623933.876 => -254.62M
-1.2345e+5 => -123,450
12.000 => 12
NaN => --
复制代码
Number Display 就是为这样的需求而建立的:wordpress
rstStr = display(-254623933.876) // result: -254.62M
复制代码
本着配置和使用分离的原则,Number Display 暴露名为 createDisplay
的高阶函数,经过传入参数给 createDisplay
进行配置,定制实际组件中析数值的 display
函数:函数
Web 中使用:工具
import createDisplay from 'number-display';
const display = createDisplay({
length: 8,
decimal: 0,
});
<div>{display(data)}</div>
复制代码
Flutter 中使用:
import 'package:number_display/number_display.dart';
final display = createDisplay(
length: 8,
decimal: 0,
);
print(display(data));
复制代码
这样使得实际组件中实用 display
的代码更为简洁,且方便批量配置。值得注意的是,在 JavaScript 中配置项是以对象的方式传入, Dart 中是以命名参数的方式传入。
Number Display 的配置项,目前包括如下 5 个:
length
:数值字符串的长度限制。为确保任何数值均可以被压缩,length 最好大于等于5,这样最极端的状况(-123000)也能被压缩到 length 限制之内。decimal
:最大小数位数。当空间不够时,实际小数位数会比这个值小,另外小数末尾的 0 会去掉.placeholder
:当值为非法数字时输出的占位符。allowText
:若是输入不是数字而是一段文本,是否原样输出。Dart 版无此参数。comma
:是否显示逗号千位分隔符。千位分隔符的意义见 这篇博客 。数值在展现时,会受到组件尺寸的约束。
理论上组件设计尺寸时须考虑到值的范围,但实际不可能沟通的这么完美,保证数值不溢出组件的任务每每仍是落到开发。
数值的展现方式能够变通,但绝对不能溢出组件以外,无疑是首要原则。这也正是 Number Display 最主要的功能。当长度不够时,Number Display 按照如下优先级对数值进行压缩:
length
小于5而且实际数值过长,将抛出异常;Number Display 处理数值的原理最主要是利用正则表达式 ,过程分为如下几步:
1. 判断合法性
对于 JavaScript ,因为其类型的灵 (sui) 活 (yi) 性,实际开发中传入的“数值” type 每每既有多是 number ,也有多是 string :
if (
(type !== 'number' && type !== 'string')
|| (type === 'number' && !Number.isFinite(value))
) {
return placeholder;
}
复制代码
而对于静态类型的 Dart,限定输入参数类型为 num 则是比较常规的作法:
if (value == null || !value.isFinite) {
return placeholder;
}
复制代码
2. 截取符号、整数、小数部分
截取一个数值的符号、整数、小数三部分,是依靠字符 "-" 和 "." 进行拆分匹配。借鉴 Ant Design 的 Statistic 组件 源码 ,咱们利用 JavaScript 中 String.match 函数一次匹配多个模式并返回对应数组的特性,只用一行代码即可同时获取三个部分:
const cells = value.match(/^(-?)(\d*)(\.(\d+))?$/);
复制代码
固然 Dart 就没有这种技巧了,三部分要分别匹配获取:
final negative = RegExp(r'^-?').stringMatch(valueStr) ?? '';
final integer = RegExp(r'\d+').stringMatch(valueStr) ?? '';
final deciRaw = RegExp(r'(?<=\.)\d+$').stringMatch(valueStr) ?? '';
复制代码
值得一提的是,Dart 语言的正则部分正在逐步完善中,我注意到最近的几个版本更新(2.1-2.4)每次都有正则语法的改动。
3. 压缩小数部分
压缩首先会去掉千位分隔符,当空间还不够时,就会按剩余空间缩减少数部分,注意因为小数点必须与小数部分共存亡,剩余空间须要以 0 和 2 两个边界分段,同时在此过程当中顺便用正则去除小数末尾的 0:
(如下代码 JavaScript 和 Dart 基本相似,仅以 JavaScript 为例)
let space = length - negative.length - int.length;
if (space >= 2) {
deciShow = deci.slice(0, space - 1).replace(/0+$/, '');
return `${negative}${int}${deciShow && '.'}${deciShow}`;
}
if (space >= 0) {
return `${negative}${int}`;
}
复制代码
4. 压缩整数部分
若是彻底去掉了小数空间仍然不够,就要将整数部分用 k 、 M 等数值单位进行压缩了。以前咱们曾给整数部分加上过千位分隔符:
const localeInt = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
复制代码
这里恰好能够经过分隔段数求得数值单位:
const sections = localeInt.split(',');
const units = ['k', 'M', 'G', 'T', 'P'];
const unit = units[sections.length - 2];
复制代码
最后和压缩小数相似,咱们要再根据剩余的空间肯定转换数值单位后的小数位数:
space = length - negative.length - mainSection.length - 1;
if (space >= 2) {
const tailShow = tailSection.slice(0, space - 1).replace(/0+$/, '');
return `${negative}${mainSection}${tailShow && '.'}${tailShow}${unit}`;
}
if (space >= 0) {
return `${negative}${mainSection}${unit}`;
}
复制代码
数值展现是经常被人忽视的一个点,自己也确实没有什么高深的技术,但若是不思考周全,会带来不少麻烦。
以上总结的需求原则尽可能作到共性,实际项目仍是会有特殊的需求,所以要尽可能作到可配置。Number Display 经历过一次大版本的迭代,对可配置的参数进行了精简,同时出于性能考虑用正则匹配替代了一些循环和判断,但愿有需求的读者对功能上提出建议: