前端如何友好的展現數值?本文基於實踐總結了一些原則,介紹封裝的工具庫 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 經歷過一次大版本的迭代,對可配置的參數進行了精簡,同時出於性能考慮用正則匹配替代了一些循環和判斷,但願有需求的讀者對功能上提出建議: