X.D 笔记X.D 笔记

在 React 中使用 D3

在 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 技术( 当然VueSvelte 也是同理 ),就可以直接由服务端输出可视化图形了。

在服务器端输出时,响应的仅有 SVG 本身,不会夹带 d3 相关的代码,更利于SEO、下载与转发。

由于不能使用DOM,服务器端输出更用于简单展示,若图形上很多的交互与页面联动,则还不能用服务端代替。

再加上 React 近年来的版本一直在服务端发力,所以 d3 在服务端的应用可能会更方便,比如在 React Canary 版本 里面有一个 createServerContext API, 在 Nextjs 等里面可直接使用, 相当于服务器端的 createContext 配合 d3 使用是相当的方便。

另外,也可以使用 react-domhydrate (混合模式),将首屏渲染交给 SSR, 将页面交互交给 CSR , 享受双重好处。