原标题:「React Hook」160行代码实现动态炫酷的可视化图表 - 排行榜
某天在逛社区时看到一帖子:
react-dynamic-charts — A React Library for Visualizing Dynamic Data
这是一个国外大佬在其公司峰会的代码竞赛中写的一个库:react-dynamic-charts,用于根据动态数据创建动态图表可视化。
它的设计非常灵活,允许你控制内部的每个元素和事件。使用方法也非常简单,其源码也是非常精炼,值得学习。
但因其提供了不少API,不利于理解源码。所以以下实现有所精简:
1.准备通用工具函数
1. getRandomColor:随机颜色
constgetRandomColor = =>{
constletters = '0123456789ABCDEF';
letcolor = '#';
for( leti = 0; i < 6; i++) {
color += letters[ Math.floor( Math.random * 16)]
}
returncolor;
};
2. translateY:填充Y轴偏移量
consttranslateY = (value) =>{
return`translateY(${value}px)`;
}
2.使用 useState Hook 生命状态变量
我们开始编写组件DynamicBarChart
constDynamicBarChart = (props) =>{
const[dataQueue, setDataQueue] = useState([]);
const[activeItemIdx, setActiveItemIdx] = useState( 0);
const[highestValue, setHighestValue] = useState( 0);
const[currentValues, setCurrentValues] = useState({});
const[firstRun, setFirstRun] = useState( false);
// 其它代码...
}
1. useState的简单理解
const[属性, 操作属性的方法] = useState(默认值);
2. 变量解析
dataQueue:当前操作的原始数据数组
activeItemIdx: 第几“帧”
highestValue: “榜首”的数据值
currentValues: 经过处理后用于渲染的数据数组
firstRun: 第一次动态渲染时间
3. 内部操作方法和对应useEffect
3.内部操作方法和对应 useEffect
请配合注释食用
// 动态跑起来~
functionstart(){
if(activeItemIdx > 1) {
return;
}
nextStep( true);
}
// 对下一帧数据进行处理
functionsetNextValues(){
// 没有帧数时(即已结束),停止渲染
if(!dataQueue[activeItemIdx]) {
iterationTimeoutHolder = null;
return;
}
// 每一帧的数据数组
constroundData = dataQueue[activeItemIdx].values;
constnextValues = {};
lethighestValue = 0;
// 处理数据,用作最后渲染(各种样式,颜色)
roundData.map( (c) =>{
nextValues[c.id] = {
...c,
color: c.color || (currentValues[c.id] || {}).color || getRandomColor
};
if( Math.abs(c.value) > highestValue) {
highestValue = Math.abs(c.value);
}
returnc;
});
// 属性的操作,触发useEffect
setCurrentValues(nextValues);
setHighestValue(highestValue);
setActiveItemIdx(activeItemIdx + 1);
}
// 触发下一步,循环
functionnextStep(firstRun = false){
setFirstRun(firstRun);
setNextValues;
}
对应useEffect:
// 取原始数据
useEffect( =>{
setDataQueue(props.data);
}, []);
// 触发动态
useEffect( =>{
start;
}, [dataQueue]);
// 设触发动态间隔
useEffect( =>{
iterationTimeoutHolder = window.setTimeout(nextStep, 1000);
return=>{
if(iterationTimeoutHolder) {
window.clearTimeout(iterationTimeoutHolder);
}
};
}, [activeItemIdx]);
useEffect示例:
useEffect( =>{
document.title = `You clicked ${count}times`;
}, [count]); // 仅在 count 更改时更新
为什么要在 effect中返回一个函数?
这是 effect可选的清除机制。每个 effect都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。
4.整理用于渲染页面的数据
constkeys = Object.keys(currentValues);
const{ barGapSize, barHeight, showTitle } = props;
constmaxValue = highestValue / 0.85;
constsortedCurrentValues = keys.sort( (a, b) =>currentValues[b].value - currentValues[a].value);
constcurrentItem = dataQueue[activeItemIdx - 1] || {};
keys: 每组数据的索引
maxValue: 图表最大宽度
sortedCurrentValues: 对每组数据进行排序,该项影响动态渲染。
currentItem: 每组的原始数据
5.开始渲染页面
大致的逻辑就是:
根据不同Props,循环排列后的数据:sortedCurrentValues
计算宽度,返回每项的label、bar、value
根据计算好的高度,触发transform
{
{
showTitle &&
}
{
sortedCurrentValues.map((key, idx) => {
const currentValueData = currentValues[key];
const value = currentValueData.value
let width = Math.abs((value / maxValue * 100));
let widthStr;
if (isNaN(width) || !width) {
widthStr = '1px';
} else {
widthStr = `${width}%`;
}
return (
{
!currentValueData.label
? key
: currentValueData.label
}