0

我在让 react-beautiful-dnd 工作而不闪烁时遇到问题。我遵循了蛋头课程中的示例。这是我的代码示例。

项目列表容器

  onDragEnd = (result) => {
    if (this.droppedOutsideList(result) || this.droppedOnSamePosition(result)) {
      return;
    }
    this.props.itemStore.reorderItem(result);
  }

  droppedOnSamePosition = ({ destination, source }) => destination.droppableId
    === source.droppableId && destination.index === source.index;

  droppedOutsideList = result => !result.destination;

  render() {
    return (
      <DragDropContext onDragEnd={this.onDragEnd}>
        <div>
          {this.props.categories.map((category, index) => (
            <ListCategory
              key={index}
              category={category}
              droppableId={category._id}
            />
          ))}
        </div>
      </DragDropContext>
    );
  }

项目类别

const ListCategory = ({
  category, droppableId,
}) => (
  <Droppable droppableId={String(droppableId)}>
    {provided => (
      <div
        {...provided.droppableProps}
        ref={provided.innerRef}
      >
        <ListTitle
          title={category.name}
        />
        <ListItems category={category} show={category.items && showIndexes} />
        {provided.placeholder}
      </div>
    )}
  </Droppable>
);

列出项目

   <Fragment>
      {category.items.map((item, index) => (
        <ListItem
          key={index}
          item={item}
          index={index}
        />
      ))}
    </Fragment>

项目

  render() {
    const {
      item, index, categoryIndex, itemStore,
    } = this.props;

    return (
      <Draggable key={index} draggableId={item._id} index={index}>
        {(provided, snapshot) => (
          <div
            role="presentation"
            className={cx({
              'list-item-container': true,
              'selected-list-item': this.isSelectedListItem(item._id),
            })}
            ref={provided.innerRef}
            {...provided.draggableProps}
            {...provided.dragHandleProps}
            style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
            onClick={this.handleItemClick}
          >
            <div className={cx('select-title')}>
             <p className={cx('list-item-name')}>{item.title}</p>
            </div>
                {capitalize(item.importance)}
            </div>
          </div>
        )}
      </Draggable>
    );
  }

重新排序项目的方法(我正在使用 Mobx-State_Tree)

    reorderItem: flow(function* reorderItem(result) {
      const { source, destination } = result;

      const categorySnapshot = getSnapshot(self.itemCategories);
      const sourceCatIndex = self.itemCategories
        .findIndex(category => category._id === source.droppableId);
      const destinationCatIndex = self.itemCategories
        .findIndex(category => category._id === destination.droppableId);
      const sourceCatItems = Array.from(categorySnapshot[sourceCatIndex].items);
      const [draggedItem] = sourceCatItems.splice(source.index, 1);

      if (sourceCatIndex === destinationCatIndex) {
        sourceCatItems.splice(destination.index, 0, draggedItem);
        const prioritizedItems = setItemPriorities(sourceCatItems);
        applySnapshot(self.itemCategories[sourceCatIndex].items, prioritizedItems);
        try {
          yield itemService.bulkEditPriorities(prioritizedItems);
        } catch (error) {
          console.error(`Problem editing priorities: ${error}`);
        }
      } else {
        const destinationCatItems = Array.from(categorySnapshot[destinationCatIndex].items);
        destinationCatItems.splice(destination.index, 0, draggedItem);

        const prioritizedSourceItems = setItemPriorities(sourceCatItems);
        applySnapshot(self.itemCategories[sourceCatIndex].items, prioritizedSourceItems);

        const prioritizedDestItems = setItemPriorities(destinationCatItems);
        applySnapshot(self.itemCategories[destinationCatIndex].items, prioritizedDestItems);
        try {
          const sourceCatId = categorySnapshot[sourceCatIndex]._id;
          const originalItemId = categorySnapshot[sourceCatIndex].items[source.index]._id;
          yield itemService.moveItemToNewCategory(originalItemId, sourceCatId, destinationCatIndex);
        } catch (error) {
          console.error(`Problem editing priorities: ${error}`);
        }
      }
    }),

样本数据

const itemData = [
    {
      _id: 'category-1',
      title: 'Backlog',
      items: [
        { _id: 'item-1', title: 'Here and back again' },
    },
    {
      _id: 'category-2',
      title: 'In progress',
      items: []
    },
   {
      _id: 'category-3',
      title: 'Done',
      items: []
    }
  }
}
概括

当和项目被拖放时,我检查该项目是否被放置在 dnd 上下文之外或放置在它被拖动的相同位置。如果是真的,我什么都不做。如果该项目被丢弃在上下文中,我检查它是否被丢弃在同一类别中。如果为真,我将项目从其当前位置移除,将其置于目标位置,更新我的状态,并进行 API 调用。

如果它被放到不同的类别中,我从源类别中删除该项目,添加到新类别,更新状态并进行 API 调用。我错过了什么吗?

4

2 回答 2

0

我相信这个问题是由同时发生的多个调度引起的。

有几件事同时发生。正在发生的一大类事情是与和相关onDragStart的事件。因为那是指示器必须向用户显示他们正在拖动以及他们从哪个项目拖动到哪个项目的地方。onDragEndonDrop

因此,特别是您需要将超时设置为onDragStart.

  const invoke = (fn: any) => { setTimeout(fn, 0) } 

因为如果您不这样做,Chrome 和其他浏览器将取消该操作。但这也是防止闪烁的关键。

const DndItem = memo(({ children, index, onItemDrop }: DndItemProps) => {
  const [dragging, setDragging] = useState(false)
  const [dropping, setDropping] = useState(false)
  const dragRef = useRef(null)
  const lastEnteredEl = useRef(null)

  const onDragStart = useCallback((e: DragEvent) => {
    const el: HTMLElement = dragRef.current
    if (!el || (
      document.elementFromPoint(e.clientX, e.clientY) !== el
    )) {
      e.preventDefault()
      return
    }
    e.dataTransfer.setData("index", `${index}`)
    invoke(() => { setDragging(true) })
  }, [setDragging])
  const onDragEnd = useCallback(() => {
    invoke(() => { setDragging(false) })
  }, [setDragging])
  const onDrop = useCallback((e: any) => {
    invoke(() => { setDropping(false) })
    const from = parseInt(e.dataTransfer.getData("index"))
    onItemDrop && onItemDrop(from, index) 
  }, [setDropping, onItemDrop])
  const onDragEnter = useCallback((e: DragEvent) => {
    lastEnteredEl.current = e.target
    e.preventDefault()
    e.stopPropagation()
    setDropping(true)
  }, [setDropping])
  const onDragLeave = useCallback((e: DragEvent) => {
    if (lastEnteredEl.current !== e.target) {
      return
    }
    e.preventDefault()
    e.stopPropagation()
    setDropping(false)
  }, [setDropping])

  return (
    <DndItemStyle
      draggable="true"
      onDragStart={onDragStart}
      onDragEnd={onDragEnd}
      onDrop={onDrop}
      onDragOver={onDragOver}
      onDragEnter={onDragEnter}
      onDragLeave={onDragLeave}
      dragging={dragging}
      dropping={dropping}
    >
      {(index < 100) && (
        cloneElement(children as ReactElement<any>, { dragRef })
      )}
    </DndItemStyle>
  )
})

invoke我必须在上面申请两个超时DndItem,原因是在下降期间,有两个很多事件相互竞争,仅举几例

  • onDragEnd,对指标进行加糖编码
  • onDrop,重新订购

我需要确保快速重新订购。因为否则你会得到双重渲染,一个带有前一个数据,一个带有下一个数据。这就是闪烁的原因。

简而言之,需要应用 React + DndsetTimeout以便调整绘制的顺序以获得最佳效果。

于 2021-08-30T13:29:32.827 回答
0

我正在使用 mst 和 react-beautiful-dnd 库我将粘贴我的 onDragEnd 操作方法

onDragEnd(result: DropResult) {
  const { source, destination } = result;

  // dropped outside the list
  if (!destination) {
    return;
  }

  if (source.droppableId === destination.droppableId) {
    (self as any).reorder(source.index, destination.index);
  }
},
reorder(source: number, destination: number) {
  const tempLayout = [...self.layout];
  const toMove = tempLayout.splice(source, 1);
  const item = toMove.pop();

  tempLayout.splice(destination + lockedCount, 0, item);

  self.layout = cast(tempLayout);
},

我认为为了避免闪烁,您需要避免使用applySnapshot

您可以替换此逻辑

      const sourceCatItems = Array.from(categorySnapshot[sourceCatIndex].items);
      const [draggedItem] = sourceCatItems.splice(source.index, 1);

      sourceCatItems.splice(destination.index, 0, draggedItem);
      const prioritizedItems = setItemPriorities(sourceCatItems);
      applySnapshot(self.itemCategories[sourceCatIndex].items, prioritizedItems);

只是树splice_items

const [draggedItem] = categorySnapshot[sourceCatIndex].items.splice(destination.index, 0, draggedItem)

这样你就不需要applySnapshot在源项目之后

于 2019-09-13T10:05:11.980 回答