在 React 中使用 D3
d3.js
d3.js
是我最喜欢的数据可视化📊工具,但它是基于DOM的,所以一般使用时,经常需要写这样的代码:
<div id="container" />
<script>
// 需要先有 id = container 的结点
const root = d3.select('#container');
// ... 代码
</script>
但写习惯了JSX后,总是这样定义,写起来还是挺麻烦的,在还没有Hook之前,需在多个函数 componentDidMounted
/ render
之中写一些耦合性代码,有了 Hook 之后,变得简单了。
使用 Hook 写法
使用useRef
,可以很方便的映射出一个DOM节点。
import { useRef, useEffect,} from 'react';
const Graph = ()=>{
const ROOT = useRef();
useEffect(() => {
const root = d3.select(ROOT.current);
// ...代码
}, []);
return <div ref={ROOT} />
}
这样写的好处,就是不再需要定义 id 或其它能定位的 selection 属性了。而使用useEffect
可以保证渲染后调用,ref必然会绑定到dom上,不会出现取不到的情况。
使用 React 输出 DOM
一般写 d3 程序,和上古时期写JQuery
,有类似,就是要操作DOM层,体现在代码里面就是所有绘制都需要使用 enter
/update
/exit
/join
/merge
等API完成。
当然也可以直接使用 React 虚拟DOM层。
import * as d3 from "d3";
const WIDTH = 300,HEIGHT=200;
const LinePlot = props => {
const x = d3.scaleLinear([0, data.length - 1], [0, WIDTH]);
const y = d3.scaleLinear(d3.extent(data), [HEIGHT, 0]);
const line = d3.line((d, i) => x(i), y);
return <svg width={WIDTH} height={HEIGHT}>
<path d={line(data)} />
{data.map((d, i) =>{
return <circle key={i} cx={x(i)} cy={y(d)} r="2.5" />
} )}
</svg>
}
这样就完全是 React 的语法了,这种写法有弊也和利。仁者见仁,智者见智。
个人认为最大的坏处就是不用 d3 相关的DOM API,会很影响交互功能,比如动画等。
而最大的好处莫过于:这种写法,是支持服务端运行的!
运行在服务端
除了 DOM/动画 和部分渲染API之外,大部分的d3 API运行是不需要浏览器环境的,可以直接运行在 Node 上面,所以如果服务端是基于 React 技术( 当然Vue
、Svelte
也是同理 ),就可以直接由服务端输出可视化图形了。
在服务器端输出时,响应的仅有 SVG 本身,不会夹带 d3 相关的代码,更利于SEO、下载与转发。
由于不能使用DOM,服务器端输出更用于简单展示,若图形上很多的交互与页面联动,则还不能用服务端代替。
再加上 React 近年来的版本一直在服务端发力,所以 d3 在服务端的应用可能会更方便,比如在 React Canary 版本
里面有一个 createServerContext
API, 在 Nextjs
等里面可直接使用, 相当于服务器端的 createContext
配合 d3 使用是相当的方便。
另外,也可以使用 react-dom
的 hydrate
(混合模式),将首屏渲染交给 SSR, 将页面交互交给 CSR , 享受双重好处。