使用自定义拖动层react.memo来反应和重新渲染太多

时间:2019-11-04 23:53:32

标签: reactjs react-dnd

我的拖放动作非常缓慢,因为重新渲染次数过多。
尽管我将所有项目都传递为基元,但React.memo似乎无济于事。

我的列表如下:

const TabList = ({ selectedTabsState, handleItemSelect, windowId, windowIndex, tabs, actions }) => {

  const { dragTabs } = actions;

  const moveTabs = ({ dragWindowId, dragTabIndex, hoverWindowId, hoverTabIndex, draggedTabs }) => {
    dragTabs({
      fromWindowId: dragWindowId,
      dragTabIndex,
      toWindowId: hoverWindowId,
      hoverTabIndex,
      draggedTabs
    });

  };

  const ref = useRef(null);
  // We need this to fix the bug that results from moving tabs from one window to a previous
  const [, drop] = useDrop({
    accept: ItemTypes.TAB,
    hover(item, monitor) {
      if (!ref.current) {
        return
      }
      const dragWindowId = item.windowId;
      const dragTabIndex = item.tabIndex;
      const hoverWindowId = windowId;

      if (hoverWindowId > dragWindowId) {
        return;
      }
      const hoverTabIndex = tabs.length;
      moveTabs({ dragWindowId, dragTabIndex, hoverWindowId, hoverTabIndex, draggedTab: item.tab });
      item.windowId = hoverWindowId;
      item.tabIndex = hoverTabIndex;
    }
  });

  drop(ref);

  const renderTab = (tab, index) => {
    const isSelected = selectedTabsState.selectedTabs.find(selectedTab => selectedTab.id === tab.id);

    return (
      <TabListItem
        key={`tab_${windowId}_${tab.id}`}
        windowId={windowId}
        windowIndex={windowIndex}
        tabIndex={index}
        isSelected={ isSelected }
        moveTabs={moveTabs}
        handleItemSelection={ handleItemSelect }
        tabId={ tab.id }
        tabUrl={ tab.url }
        tabTitle={ tab.title }

    />)
  };

  return (
    <li>
      <ul className="nested-list">
        { tabs.map((tab, index) => renderTab(tab, index)) }
      </ul>
      <div ref={ ref } className='nested-list-bottom'></div>
    </li>
  );
};

const mapDispatchToProps = (dispatch) => {
  return {
    actions: bindActionCreators(
      Object.assign({}, CurrentWindowsActions)
      , dispatch)
  }
};

const mapStateToProps = state => {
  return {
    selectedTabsState: state.selectedTabs
  };
};


export default connect(mapStateToProps, mapDispatchToProps)(TabList);

我的列表项如下:

const collect = (connect, monitor) => ({
  // Call this function inside render()
  // to let React DnD handle the drag events:
  connectDragSource: connect.dragSource(),
  // You can ask the monitor about the current drag preview
  connectDragPreview: connect.dragPreview(),
  // You can ask the monitor about the current drag state:
  isDragging: monitor.isDragging(),
});

// We use dragSource to add custom isDragging
/* const tabSource = {
  beginDrag({ selectedTabsState }) {
    return { selectedTabs: selectedTabsState.selectedTabs };
  }
}; */ // --> this is also problematic... I can never pass selectedTabsState to the item to be used in the drag layer, because it will re-render all items as well, and it is required to be passed as parameter to DragSource.

const tabSource = {
  beginDrag() {
    return {selectedTabs: [{id: 208}]};
  }
};


const TabListItem = React.memo(
  ({ connectDragPreview, isSelected, handleItemSelection, connectDragSource, isDragging, windowId, windowIndex, tabId, tabUrl, tabTitle, tabIndex, moveTabs }) => {

  useEffect(() => {
    // Use empty image as a drag preview so browsers don't draw it
    // and we can draw whatever we want on the custom drag layer instead.
    connectDragPreview(getEmptyImage(), {
      // IE fallback: specify that we'd rather screenshot the node
      // when it already knows it's being dragged so we can hide it with CSS.
      captureDraggingState: true
    });
  }, []);

  const ref = useRef(null);
  const [, drop] = useDrop({
    accept: ItemTypes.TAB,
    hover(item, monitor) {
      if (!ref.current) {
        return
      }
      const dragWindowId = item.windowId;
      const dragTabIndex = item.tabIndex;

      const hoverWindowId = windowId;
      const hoverTabIndex = tabIndex;

      // Don't replace items with themselves
      if (dragTabIndex === hoverTabIndex && dragWindowId === hoverWindowId) {
        return
      }
      // Determine rectangle on screen
      const hoverBoundingRect = ref.current.getBoundingClientRect();
      // Get vertical middle
      const hoverMiddleY =
        (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
      // Determine mouse position
      const clientOffset = monitor.getClientOffset();

      // Get pixels to the top
      const hoverClientY = clientOffset.y - hoverBoundingRect.top;
      // Only perform the move when the mouse has crossed half of the items height
      // When dragging downwards, only move when the cursor is below 50%
      // When dragging upwards, only move when the cursor is above 50%
      // Dragging downwards
      if (dragTabIndex < hoverTabIndex && hoverClientY < hoverMiddleY) {
        return
      }
      // Dragging upwards
      if (dragTabIndex > hoverTabIndex && hoverClientY > hoverMiddleY) {
        return
      }
      // Time to actually perform the action
      moveTabs({ dragWindowId, dragTabIndex, hoverWindowId, hoverTabIndex, draggedTabs: item.selectedTabs });
      // Note: we're mutating the monitor item here!
      // Generally it's better to avoid mutations,
      // but it's good here for the sake of performance
      // to avoid expensive index searches.
      item.tabIndex = hoverTabIndex;
    }
  });

  drop(ref);
  console.log('render');
  return connectDragSource(
    <li ref={ ref }
        style={ getTabStyle(isDragging, isSelected) }
        onClick={(e) => handleItemSelection(e.metaKey, e.shiftKey, tabId, tabIndex)}
    >
      <div className='nested-list-item'>
        <div>{ tabTitle }</div>
        <a className='url' target="_blank" href={tabUrl}>{tabUrl}</a>
      </div>
    </li>
  );
});

export default DragSource(ItemTypes.TAB, tabSource, collect)(TabListItem);

该代码一次只能拖动选定的项目(必须在自定义拖动层中显示);它不会引发异常(它可以工作),但是它慢得要命。
在我的控制台中,我可以看到该项目被渲染了48次,这是我拥有的列表项目的数量。这使拖动变得非常不稳定,并且随着更多的列表项变得越来越不稳定。

有人知道为什么React.memo在我的情况下不起作用吗?

编辑:我发现部分不连贯性来自以下事实:当涉及多个列表项被拖动时,悬停代码无法正确计算。并不能消除只拖动几个列表项就不需要重新渲染所有列表项的事实。

1 个答案:

答案 0 :(得分:0)

我不确定为什么尽管使用React.memo仍会重新渲染所有项目,但是我通过修复以下代码摆脱了混乱:

const tabSource = {
  beginDrag({ selectedTabsState, windowId, tabIndex }) {
    return { windowId, tabIndex, selectedTabs: selectedTabsState.selectedTabs };
  }
};

这导致拖动悬停计算触发太多状态更新,然后在redux更新状态时进行大量重新渲染。