无法实现D3.js与useRef进行交互。正确的方法是什么?

时间:2019-05-27 12:05:07

标签: reactjs typescript d3.js

我正在尝试使用d3.js创建折线图,并将其实现为带有钩子的功能组件。

我尝试使用useRef。将它们初始化为null,然后在JSX中进行设置,如代码中所示。当我要使用使用clientWidth ref的lineChart时,它表示为空。

我想到创建一个initializeDrawing函数,该函数将初始化空的赋值,但是由于组件不会在变量更改时刷新,因此它不起作用。我还考虑过将这些变量携带到useState中,但这似乎不是一个好的解决方案。

我想向您学习什么是用功能组件实现d3.js图表​​的最佳解决方案。

我正在尝试实现this example,但是学习实现d3.js以使函数组件具有钩子的一般原则是主要目标。

代码如下:

...
...
...
const LineChart: FunctionComponent = (props) => {
  let dataArrIndex = 0;
  const lineChart = useRef((null as unknown) as HTMLDivElement);
  const xAxis = useRef((null as unknown) as SVGGElement);
  const yAxis = useRef((null as unknown) as SVGGElement);
  const margin = { top: 40, right: 40, bottom: 40, left: 40 };
  const aspectRatio = 9 / 16;
  const chartWidth = lineChart.current.clientWidth - margin.left - margin.right;
  const chartHeight =
    lineChart.current.clientWidth * aspectRatio - margin.top - margin.bottom;
  const yScale = d3.scaleLinear().range([chartHeight, 0]);
  const xScale = d3.scaleBand().range([0, chartWidth]);
  const update = () => {
    yScale.domain([
      0,
      d3.max(
        data,
        (d): number => {
          return d[dataArrIndex].balance;
        },
      ) as number,
    ]);

    xScale.domain(data[dataArrIndex].map((d) => d.month));

    d3.select(yAxis.current).call(d3.axisLeft(yScale));
    d3.select(xAxis.current).call(d3.axisBottom(xScale));
  };
  return (
    <div className="line-chart" ref={lineChart}>
      <svg width={chartWidth} height={chartHeight}>
        <g transform={`translate(${margin.left},${margin.bottom})`} />
        <g ref={yAxis} />
        <g ref={xAxis} />
        <path />
      </svg>
    </div>
  );
};

export default LineChart;


1 个答案:

答案 0 :(得分:0)

问题是您尝试在dom中创建此元素之前对元素使用ref,因此您具有null而不是对htmlelement的引用。要解决您的问题,您需要包装所有使用refs来使用useEffect挂钩的代码。

类似这样的东西:

useEffect(() => {
  const chartWidth = lineChart.current.clientWidth - margin.left - margin.right;
  const chartHeight =
    lineChart.current.clientWidth * aspectRatio - margin.top - margin.bottom;
  const yScale = d3.scaleLinear().range([chartHeight, 0]);
  const xScale = d3.scaleBand().range([0, chartWidth]);
  const update = () => {
    yScale.domain([
      0,
      d3.max(
        data,
        (d): number => {
          return d[dataArrIndex].balance;
        },
      ) as number,
    ]);

    xScale.domain(data[dataArrIndex].map((d) => d.month));

    d3.select(yAxis.current).call(d3.axisLeft(yScale));
    d3.select(xAxis.current).call(d3.axisBottom(xScale));
  };
}

使用效果在功能组件中用作componentDidMount / componentDidUpdate / componentWillUnmount的类似物

如果每次组件接收更新时都需要调用方法“ update”,则需要将其从useEffect移到函数体并在ueEffect中调用:

const update = () => {
    yScale.domain([
      0,
      d3.max(
        data,
        (d): number => {
          return d[dataArrIndex].balance;
        },
      ) as number,
    ]);

    xScale.domain(data[dataArrIndex].map((d) => d.month));

    d3.select(yAxis.current).call(d3.axisLeft(yScale));
    d3.select(xAxis.current).call(d3.axisBottom(xScale));
  };

useEffect(() => {
  const chartWidth = lineChart.current.clientWidth - margin.left - margin.right;
  const chartHeight =
    lineChart.current.clientWidth * aspectRatio - margin.top - margin.bottom;
  const yScale = d3.scaleLinear().range([chartHeight, 0]);
  const xScale = d3.scaleBand().range([0, chartWidth]);
  update();
}

您可以通过多种方式使用宽度高度之类的属性:

  1. 使用useState挂钩将其设置为状态:const {width,setWidth} = useState(0)并从ref的useEffect中对其进行更新

  2. 检查ref!==空<svg width={chartRef && chartRef.current.offsetWidth} />