首页 > 解决方案 > React with D3 - 只渲染新项目而不更新或删除旧项目

问题描述

我创建了一个按预期工作的尺寸图例,除了当我对其进行更改(例如更新数据)时 - 旧版本保持呈现,新版本呈现在顶部。如何按照 D3 的预期实现更新、添加和删除元素?

我知道 React 和 D3 都想控制 DOM,所以很难一起工作,而且我的代码到目前为止来自非反应教程 - 所以我相信错误一定与我的方式有关一起使用 React 和 D3。

这是我的代码

import React, { useRef, useEffect } from 'react';
import { select, scaleSqrt } from 'd3';

function D3SizeBar(props) {

    const svgRef = useRef();

    useEffect(() => {

        const sizeLegend = (selection, props) => {
            const {
                sizeScale,
                spacing,
                textOffset,
                numTicks,
                circleFill
            } = props;
        
        const ticks = sizeScale.ticks(numTicks).filter(d => d !== 0)
        
        const groups = selection.selectAll('g').data(ticks);
    
        const groupsEnter = groups.enter().append('g');
    
        groupsEnter
            .merge(groups)
            .attr(`transform`, (d, i) =>
                `translate(0, ${i * spacing})`
            );
    
        groups
            .exit()
            .remove();
        
        groupsEnter
            .append('circle')
            .merge(groups.select('circle'))
            .attr('r', sizeScale)
            .attr('fill', circleFill)
        
        groupsEnter
            .append('text')
            .merge(groups.select('text'))
            .text(d => d)
            .attr('dy', '0.32em')
            .attr('x', d => sizeScale(d) + textOffset)
        }

        const data = [0, 0]
        data.splice(0, 1, Math.max.apply(Math, props.sValueArray))
        data.splice(1, 1, Math.min.apply(Math, props.sValueArray))
        console.log(data)

        const sizeScale = scaleSqrt()
            .domain(data)
            .range([20, 5])

        const svg = select(svgRef.current);
        svg
            .append('g')
            .attr('transform', `translate(30,20)`)
            .call(sizeLegend, {
                sizeScale,
                spacing: 25,
                textOffset: 10,
                numTicks: 3,
                circleFill: 'rgba(0, 0, 0, 0.5)'
            }
        );
    }, [props.sValueArray]);

    return (
        <svg ref={svgRef}/>
    )
}

export default D3SizeBar;

如果有人足够精通同时使用 React 和 D3,我将非常感谢您对我哪里出错的帮助。`sValueArray' 只是一个具有最大值的数字数组。值 = 50 和最小值。值 = 10。

这里有一个例子,将间距从 25 更改为 30。

间距从 25 更改为 30 时的 D3SizeBar 组件

标签: javascriptreactjsd3.jsreact-hooksrender

解决方案


这是因为您在添加其他元素之前没有清除元素的内容。

您正在ref从元素中获取<svg />元素,并通过选择元素d3.select (ref.current),但 ref每次useEffect运行时都保持不变,并且在您运行时select (ref.current)它已经有了孩子。因为每次React发送一个新的网络更新, useEffect都被更新并ref保持不变。

解决方案

svg在添加新对象之前,您可以简单地清理父对象中存在的所有子对象:

svg.selectAll("*").remove();

代码

import React, { useRef, useEffect } from 'react';
import { select, scaleSqrt } from 'd3';

function D3SizeBar(props) {

    const svgRef = useRef();

    useEffect(() => {

        const sizeLegend = (selection, { sizeScale,
            spacing,
            textOffset,
            numTicks,
            circleFill }) => {

            const ticks = sizeScale.ticks(numTicks).filter(d => d !== 0)

            const groups = selection.selectAll('g').data(ticks);

            const groupsEnter = groups.enter().append('g');

            groupsEnter
                .merge(groups)
                .attr(`transform`, (d, i) =>
                    `translate(0, ${i * spacing})`
                );

            groups
                .exit()
                .remove();

            groupsEnter
                .append('circle')
                .merge(groups.select('circle'))
                .attr('r', sizeScale)
                .attr('fill', circleFill)

            groupsEnter
                .append('text')
                .merge(groups.select('text'))
                .text(d => d)
                .attr('dy', '0.32em')
                .attr('x', d => sizeScale(d) + textOffset)
        }

        const data = [0, 0]
        data.splice(0, 1, Math.max.apply(Math, props.sValueArray))
        data.splice(1, 1, Math.min.apply(Math, props.sValueArray))
        console.log(data)

        const sizeScale = scaleSqrt()
            .domain(data)
            .range([20, 5])

        const svg = select(svgRef.current);
        svg.selectAll("*").remove() // add this before the append.
        svg
            .append('g')
            .attr('transform', `translate(30,20)`)
            .call(sizeLegend, {
                sizeScale,
                spacing: 50,
                textOffset: 10,
                numTicks: 3,
                circleFill: 'rgba(0, 0, 0, 0.5)'
            }
            );
    }, [props.sValueArray]);

    return (
        <svg ref={svgRef} />
    )
}

export default D3SizeBar;

推荐阅读