reactjs 通过拖动和选择对SVG进行大数组渲染

pkwftd7m  于 2022-12-03  发布在  React
关注(0)|答案(3)|浏览(149)

bounty将在6天后过期。回答此问题可获得+150的声望奖励。Marty正在寻找标准答案

我正在使用React + d3js在svg上呈现数据,我有一个包含如下数据的大型数组

const arrayItemExample = {
  id: 1,
  text: 'Some Text for node',
  x: 100,
  y: 300,
  selected: false,
  dragging: false
}

这类元素的列表可以达到数万个,主要问题是当我点击或拖动一个呈现的元素时,我会更新该元素上的数据:

const handleDragStart = useCallback((id)=>{
  setData(data=>{
    return data.map(item=>{
      if(item.id === id){
        return {
          ...item,
          dragging: true
        }
      }
      return item
    })
  })
},[])

const handleSelect = useCallback((id)=>{
  setData(data=>{
    return data.map(item=>{
      if(item.id === id){
        return {
          ...item,
          selected: true
        }
      }
      return item
    })
  })
},[])

这两个函数都适用于少量数据,但如果页面上有100个或更多元素,则在重绘元素期间单击或拖动元素会降低页面速度。
是否有任何方法可以更新特定元素中的数据,只重绘一个元素?我无法使用组件的内部状态,因为我需要所选元素和可拖动元素的总数据:例如,我已经用ctrl选择了几个元素,当我开始拖动其中一个选定元素时,其他元素也必须被拖动

gijlo24d

gijlo24d1#

D3数据集通常使用SVG呈现,SVG是一种保留模式图形模型,易于使用,但性能有限。SVG图表通常可以处理大约1,000个数据点。
从D3 v4开始,您还可以选择使用canvas渲染图表,这是一种即时模式图形模型。使用Canvas,您可以渲染大约10,000个数据点,同时保持流畅的60fps交互
因此我建议使用Canvas而不是SVG。如果您有时间,我建议您使用WebGL。

lsmepo6l

lsmepo6l2#

要更新数组中特定元素的数据而不重绘所有元素,可以将useReducer挂接与useMemo挂接结合使用。useReducer挂接允许您定义一个reducer函数,该函数根据所调度的操作更新状态。useMemo挂接允许您计算一个记忆值,该值仅在其中一个依赖项发生更改时才重新计算。
下面的示例说明如何使用useReducer和useMemo挂接更新数组中特定元素的数据,而无需重新绘制所有元素:

const [state, dispatch] = useReducer(reducer, initialState);

const data = useMemo(() => {
  return state.data.map(item => {
    if (item.id === state.selectedId) {
      return {
        ...item,
        selected: true
      };
    } else if (item.id === state.draggingId) {
      return {
        ...item,
        dragging: true
      };
    }
    return item;
  });
}, [state.data, state.selectedId, state.draggingId]);

const handleDragStart = useCallback((id) => {
  dispatch({ type: 'DRAG_START', id });
}, []);

const handleSelect = useCallback((id) => {
  dispatch({ type: 'SELECT', id });
}, []);

在上面的代码中,我们使用useReducer挂钩定义了一个reducer函数。reducer函数接受当前状态和一个操作,并根据该操作返回一个新的状态。我们还定义了两个操作创建器handleDragStart和handleSelect,它们分别调度DRAG_START和SELECT操作。
接下来,我们使用useMemo钩子计算数据数组的记忆版本。useMemo钩子接受一个函数,该函数返回要记忆的值和依赖项列表。在本例中,该函数返回更新的数据数组,其中的选定元素和拖动元素根据当前状态中的selectedId和draggingId更新。
最后,我们使用React组件中的数据数组来呈现元素。

wsxa1bj1

wsxa1bj13#

There are a few ways you can optimize the rendering of your data to improve performance.
One approach you can take is to use the shouldComponentUpdate lifecycle method in your React components. This method allows you to control when a component should be updated, which can help prevent unnecessary re-renders. For example, you can use shouldComponentUpdate to only re-render a component when its selected or dragging prop has changed, rather than re-rendering every time the parent component's data array is updated.
Another approach is to use a virtualized list component, such as the react-virtualized library. A virtualized list only renders the elements that are currently visible on the screen, which can significantly improve the performance of rendering large lists of data.
In addition, it's generally a good idea to optimize your d3.js code to avoid unnecessarily re-rendering elements. For example, you can use the .exit() and .enter() selections in d3 to only update the elements that have been added or removed from the data, rather than re-rendering the entire list of elements every time the data changes.
To use the shouldComponentUpdate method in your React components, you can add the method to your component class like this:

class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps) {
    // Only update the component if the selected or dragging prop has changed
    return this.props.selected !== nextProps.selected || this.props.dragging !== nextProps.dragging;
  }

  render() {
    // Render the component
  }
}

To use the react-virtualized library, you can install it from npm and then use the VirtualizedList component in your code like this:

import { VirtualizedList } from 'react-virtualized';

const MyList = ({ data }) => {
  return (
    <VirtualizedList
      data={data}
      // Specify the height and width of the list
      height={300}
      width={300}
      // Specify a function to render each item in the list
      renderItem={({ item, index }) => {
        return <MyComponent key={item.id} item={item} />;
      }}
    />
  );
};

Here's an example of using the .exit() and .enter() selections in d3 to only update the elements that have been added or removed from the data, rather than re-rendering the entire list of elements every time the data changes:

// Select all elements with the specified class
const elements = d3.selectAll('.my-element-class');

// Bind the data to the selection
const updatedElements = elements.data(myData);

// Remove any elements that are no longer in the data
updatedElements.exit().remove();

// Add new elements for any data that doesn't have a corresponding element
const newElements = updatedElements.enter().append('div')
  .classed('my-element-class', true)
  // Set the initial attributes of the new elements
  .attr('x', d => d.x)
  .attr('y', d => d.y)
  .text(d => d.text);

// Update the attributes of all elements, whether they are new or not
updatedElements.merge(newElements)
  .attr('x', d => d.x)
  .attr('y', d => d.y)
  .text(d => d.text);

This code will select all elements with the my-element-class class, bind the specified data to the selection, remove any elements that are no longer in the data, add new elements for any data that doesn't have a corresponding element, and update the attributes of all elements, whether they are new or not. This allows you to only update the elements that have changed, rather than re-rendering the entire list of elements every time the data changes.

相关问题