React-dnd: Kinerja lambat CustomDragLayer

Dibuat pada 7 Des 2016  ·  29Komentar  ·  Sumber: react-dnd/react-dnd

Apakah ada alasan mengapa CustomDragLayer bagi saya memberi saya kinerja yang buruk? Ini seperti fps rendah, atau lebih tepatnya, objek sewenang-wenang yang saya seret tertinggal dan tidak terlihat mulus

Komentar yang paling membantu

Sepertinya react-dnd-html5-backend memiliki kinerja yang buruk dengan DragLayer khusus sedangkan react-dnd-touch-backend memiliki kinerja yang OK.

Semua 29 komentar

Saya juga mengalami ini. Masalahnya di sini adalah, bahkan jika Anda membuat komponen murni yang tidak berubah di lapisan seret, perubahan offset memicu setidaknya rekonsiliasi reaksi pada setiap gerakan mouse. Ini sangat tidak efisien jika ada kasus umum, di mana lapisan seret seharusnya hanya menampilkan elemen pratinjau khusus yang tidak berubah.

Untuk mengatasinya, saya menduplikasi DragLayer.js yang melakukan rendering offset untuk saya dan ini dengan cara berkinerja tinggi, yang berarti mengubah gaya wadah yang bergerak di sekitar pratinjau secara langsung.

// PerformantDragLayer.js
/* eslint-disable */
import React, { Component, PropTypes } from 'react';
import shallowEqual from 'react-dnd/lib/utils/shallowEqual';
import shallowEqualScalar from 'react-dnd/lib/utils/shallowEqualScalar';
import isPlainObject from 'lodash/isPlainObject';
import invariant from 'invariant';
import checkDecoratorArguments from 'react-dnd/lib/utils/checkDecoratorArguments';
import hoistStatics from 'hoist-non-react-statics';

function layerStyles(isDragging) {
  return {
    position: 'fixed',
    pointerEvents: 'none',
    zIndex: 1,
    left: 0,
    top: 0,
    width: isDragging ? '100%' : 0,
    height: isDragging ? '100%' : 0,
    opacity: isDragging ? 1 : 0
  };
}

export default function DragLayer(collect, options = {}) {
  checkDecoratorArguments('DragLayer', 'collect[, options]', ...arguments);
  invariant(
    typeof collect === 'function',
    'Expected "collect" provided as the first argument to DragLayer ' +
    'to be a function that collects props to inject into the component. ',
    'Instead, received %s. ' +
    'Read more: http://gaearon.github.io/react-dnd/docs-drag-layer.html',
    collect
  );
  invariant(
    isPlainObject(options),
    'Expected "options" provided as the second argument to DragLayer to be ' +
    'a plain object when specified. ' +
    'Instead, received %s. ' +
    'Read more: http://gaearon.github.io/react-dnd/docs-drag-layer.html',
    options
  );

  return function decorateLayer(DecoratedComponent) {
    const { arePropsEqual = shallowEqualScalar } = options;
    const displayName =
      DecoratedComponent.displayName ||
      DecoratedComponent.name ||
      'Component';

    class DragLayerContainer extends Component {
      static DecoratedComponent = DecoratedComponent;

      static displayName = `DragLayer(${displayName})`;

      static contextTypes = {
        dragDropManager: PropTypes.object.isRequired
      }

      getDecoratedComponentInstance() {
        return this.refs.child;
      }

      shouldComponentUpdate(nextProps, nextState) {
        return !arePropsEqual(nextProps, this.props) ||
          !shallowEqual(nextState, this.state);
      }

      constructor(props, context) {
        super(props);
        this.handleOffsetChange = this.handleOffsetChange.bind(this);
        this.handleStateChange = this.handleStateChange.bind(this);

        this.manager = context.dragDropManager;
        invariant(
          typeof this.manager === 'object',
          'Could not find the drag and drop manager in the context of %s. ' +
          'Make sure to wrap the top-level component of your app with DragDropContext. ' +
          'Read more: http://gaearon.github.io/react-dnd/docs-troubleshooting.html#could-not-find-the-drag-and-drop-manager-in-the-context',
          displayName,
          displayName
        );

        this.state = this.getCurrentState();
      }

      componentDidMount() {
        this.isCurrentlyMounted = true;

        const monitor = this.manager.getMonitor();
        this.unsubscribeFromOffsetChange = monitor.subscribeToOffsetChange(
          this.handleOffsetChange
        );
        this.unsubscribeFromStateChange = monitor.subscribeToStateChange(
          this.handleStateChange
        );

        this.handleStateChange();
      }

      componentWillUnmount() {
        this.isCurrentlyMounted = false;

        this.unsubscribeFromOffsetChange();
        this.unsubscribeFromStateChange();
      }

      handleOffsetChange() {
        if (!this.isCurrentlyMounted) {
          return;
        }

        const monitor = this.manager.getMonitor();
        const offset = monitor.getSourceClientOffset();
        const offsetDiv = this.refs.offset;
        if (offset && offsetDiv) {
          offsetDiv.style.transform = `translate(${offset.x}px, ${offset.y}px)`;
        }
      }

      handleStateChange() {
        if (!this.isCurrentlyMounted) {
          return;
        }

        const nextState = this.getCurrentState();
        if (!shallowEqual(nextState, this.state)) {
          this.setState(nextState);
        }
      }

      getCurrentState() {
        const monitor = this.manager.getMonitor();
        return {
          collected: collect(monitor),
          isDragging: monitor.isDragging()
        };
      }

      render() {
        return (
          <div style={layerStyles(this.state.isDragging)}>
            <div ref='offset'>
              {this.state.isDragging && <DecoratedComponent {...this.props} {...this.state.collected} ref='child' />}
            </div>
          </div>
        );
      }
    }

    return hoistStatics(DragLayerContainer, DecoratedComponent);
  };
}

Ini kemudian dapat digunakan dengan cara berikut:

// MyCustomDragLayer.js
import PerformantDragLayer from './PerformantDragLayer'
import React, {PropTypes} from 'react'

class CustomDragLayerRaw extends React.Component {
  static propTypes = {
    item: PropTypes.any,
    itemType: PropTypes.string
  }

  render () {
    const {item, itemType} = this.props
    return <div>{itemType}</div>
  }
}

export default PerformantDragLayer((monitor) => ({
  item: monitor.getItem(),
  itemType: monitor.getItemType()
})(CustomDragLayerRaw)

Peningkatan kinerja sangat terlihat:

drag-layer-default

drag-layer-performant

Tentu saja default DragLayer masih lebih fleksibel. Implementasi saya yang lebih berkinerja hanya lebih cepat, karena menangani kasus khusus. Tapi saya kira kasus khusus ini sangat umum.

@gaearon Apakah implementasi yang terspesialisasi tetapi jauh lebih berkinerja menarik bagi Anda untuk diintegrasikan sebagai alternatif ke dalam reaksi-dnd?

@choffmeister Hai. Terima kasih untuk posting Anda. Saya melihatnya sekilas sehari yang lalu tetapi hanya melihatnya secara mendalam saat itu. Kode yang Anda posting tampaknya sangat terspesialisasi dalam implementasi Anda, bisakah Anda menunjukkan aspek penting yang diperlukan untuk ini? Dan bagaimana CustomDragLayerRaw digunakan akan sangat bermanfaat juga.

Saya tidak yakin apakah semua plugin, pustaka, dan kode lain yang Anda miliki adalah bagian dari solusi atau tidak.

Terima kasih

Saya telah bereksperimen dan berhasil membuatnya bekerja. Tampaknya konten yang Anda inginkan di "seret hantu" Anda harus berada di div 'offset'. Saya tidak tahu cara optimal untuk melakukan ini, tetapi dalam pengujian saya, saya hanya meletakkan div yang mirip dengan konten yang diseret. Saya belum membuat item lapisan seret sama dengan item yang diseret, tetapi meneruskan beberapa data dan mengubah status item lapisan seret tidak terlalu sulit untuk dilakukan.

Performanya sangat bagus, meskipun hanya berfungsi saat menyeret di luar wadah. Jika Anda menyeret dalam lingkaran di sekitar item, kinerjanya juga sempurna seperti kasus sebelumnya (yaitu Anda memiliki daftar 4 item, menyeret dan menahan mouse di area item ke-4). Namun, jika Anda menggambar lingkaran dengan mouse Anda, menyeret, di semua 4 elemen, masih tertinggal, seperti sebelumnya, tetapi dengan sedikit peningkatan fps (meskipun masih laggy). Tapi implementasi ini adalah awal yang baik. Setidaknya untuk sebagian besar kasus umum ketika Anda ingin menyeret dari satu wadah ke wadah lain, maka ini pasti akan menjaga UX tetap bagus.

Apakah yang Anda maksud: laggy

  1. Apakah FPSnya terlalu rendah sehingga terlihat gagap, atau
  2. apakah elemen bergerak dengan lancar, tetapi saat bergerak selalu berada agak jauh di belakang gerakan mouse?

Saya akan mengatakan keduanya

Setelah penggunaan lebih lanjut, saya menyadari bahwa solusinya tidak benar-benar menghilangkan kelambatan sama sekali. Saya memiliki div kartu yang memiliki sedikit fitur yang tenang di dalamnya. Solusi Anda pada awalnya memberikan peningkatan fps yang sangat besar dibandingkan dengan contoh di dokumen, namun, karena saya membuat div saya lebih rumit, itu mulai tertinggal seperti saya tidak memiliki ini.

Saya tidak tahu apakah ui semantiknya menyebabkan ini atau bereaksi dnd. Jika ada cara lain yang lebih optimal untuk membuat lapisan seret sangat berkinerja, saya sangat ingin tahu.

Juga jika ada yang ingin mencoba dan melihat apakah itu juga tertinggal untuk mereka, saya telah menggunakan kartu UI Semantik, dengan banyak elemen di dalamnya. Kartu placeholder saya memiliki 2 judul, 5 "label" semantik, 3 "ikon" semantik, dan gambar avatar.

Setelah pengujian lebih lanjut dengan contoh yang berbeda ini di: http://react-trello-board.web-pal.com/ (implementasinya sangat mirip dengan apa yang telah diposting choffmeister sebelumnya), saya masih mendapatkan jeda ketika mencoba menyeret div rumit saya . Saya senang bahwa ini adalah kesimpulan yang saya dapatkan, karena saya tidak perlu bereksperimen lagi dengan kode; masalahnya terletak pada div saya, yang dapat dengan mudah diperbaiki.

Saya masih mengalami ini dengan sangat buruk menggunakan kode yang SANGAT mirip dengan contoh di dokumen. Itu pasti perhitungan transformasi yang membuat setiap mouse kecil bergerak?

Adakah orang lain yang menemukan solusi untuk ini?

Saya mengalami masalah kinerja, bahkan dengan PureComponents. Menggunakan fitur 'Sorotan Pembaruan' di plugin Chrome React Dev untuk mendiagnosis sepertinya semua komponen saya diperbarui ketika prop currentOffset diubah. Untuk mengurangi saya memastikan bahwa DragLayer yang lewat di prop ini hanya berisi drag preview itu sendiri, meskipun sudah ada DragLayer di tingkat atas aplikasi untuk melacak item apa yang sedang diseret.

Saya belum mencoba mengadaptasi solusi @choffmeister tetapi sepertinya itu juga akan menyelesaikan masalah. Saya pikir mungkin hal seperti itu harus dipertimbangkan untuk pustaka inti karena saya hanya bisa membayangkan bahwa ini adalah masalah umum bagi siapa pun yang menerapkan pratinjau seret.

Saya memastikan bahwa DragLayer yang lewat di prop ini hanya berisi drag preview itu sendiri, meskipun sudah ada DragLayer di tingkat atas aplikasi

Saya tidak yakin saya mengikuti! Saya hanya memiliki satu lapisan seret khusus di aplikasi saya, saya percaya!

Ini adalah masalah khusus untuk kasus penggunaan saya – lapisan seret sudah digunakan sebelum pratinjau seret khusus diperlukan, untuk melacak item apa yang sedang diseret. Awalnya saya berpikir masuk akal untuk menggunakan lapisan seret yang sama untuk melakukan pratinjau seret khusus ketika saya menambahkannya tetapi, karena ini berada di tingkat atas aplikasi saya, mengubah alat peraga untuk ini pada setiap gerakan kecil menyebabkan banyak pembaruan komponen tambahan.

Gotcha, terima kasih.

Saya menyederhanakan lapisan seret saya menjadi satu komponen bodoh, sejauh yang saya tahu itu adalah perhitungan x/y konstan yang menyebabkan kelambatan.

Saya belum cukup mengikuti apa yang dilakukan @choffmeister di atas, tetapi sepertinya saya harus melihat lebih dekat.

@gaearon Ini tampaknya menjadi masalah yang cukup baik untuk lib, ada saran tentang perbaikan yang tepat?

Sepertinya react-dnd-html5-backend memiliki kinerja yang buruk dengan DragLayer khusus sedangkan react-dnd-touch-backend memiliki kinerja yang OK.

Agak jelas, tetapi ini membantu:

let updates = 0;
@DragLayer(monitor => {
  if (updates++ % 2 === 0) {
    return {
      currentOffset: monitor.getSourceClientOffset()
    }
  }
  else {
    return {}
  }
})

Solusi lain adalah dengan menggunakan paket react-throttle-render untuk membatasi metode render DragLayer.

https://www.npmjs.com/package/react-throttle-render

Terima kasih @aks427 Saya akan mencobanya dan melihat apakah itu meningkat lebih lanjut (menggunakan lapisan seret khusus seperti di atas juga yang banyak membantu dalam KEBANYAKAN kasus tetapi tidak semua)

@dobesv berubah menjadi react-dnd-touch-backend memecahkan masalah. Saya juga mencoba react-throttle-render, tetapi sepertinya masih tidak bagus.

Ya, saya tahu backend sentuh berfungsi, tetapi tidak mendukung unggahan seret dan lepas file, yang ingin saya gunakan.

@dobesv kami menggunakan https://react-dropzone.js.org/ untuk drag and drop file :) mungkin itu baik untuk Anda juga.

@dobesv abaikan komentar saya. react-dropzone hanya mendukung file drop.

tetapi untuk senario yang lebih umum, jika file tersebut perlu diunggah, file tersebut harus keluar dari browser. kita hanya perlu file drop harus ok.

Agak tidak punya otak, tapi saya menggunakan perbaikan lapisan drag performant dan menghabiskan a
BANYAK waktu melihat kode saya untuk membatasi jumlah render yang
dipanggil oleh aplikasi (banyak beralih Komponen -> PureComponent) dan
pada akhirnya saya bahkan tidak perlu menggunakan react-throttle-render.

Saya PASTI merekomendasikan untuk menggunakan PureComponent dan mencoba untuk
maksimalkan kode Anda alih-alih mencari perbaikan yang mudah di utas ini jika
render Anda lambat! Saya akan memposting lebih banyak jika kami meningkatkan model Performant di
semua meskipun!

>

@framerate Bahkan menggunakan PureComponents itu menjadi masalah karena algo rekonsiliasi mahal untuk dijalankan pada setiap gerakan mouse. Cukup profilkan aplikasi Anda dengan Chrome DevTools dan CPU throttling ke 4X, yang merupakan pelambatan realistis untuk perangkat seluler kelas menengah.

Untuk anak cucu dan siapa pun yang berjuang dengan kinerja seret dan Anda tidak ingin menarik banyak kode seperti pada solusi @choffmeister :

let subscribedToOffsetChange = false;

let dragPreviewRef = null;

const onOffsetChange = monitor => () => {
  if (!dragPreviewRef) return;

  const offset = monitor.getSourceClientOffset();
  if (!offset) return;

  const transform = `translate(${offset.x}px, ${offset.y}px)`;
  dragPreviewRef.style["transform"] = transform;
  dragPreviewRef.style["-webkit-transform"] = transform;
};

export default DragLayer(monitor => {
  if (!subscribedToOffsetChange) {
    monitor.subscribeToOffsetChange(onOffsetChange(monitor));
    subscribedToOffsetChange = true;
  }

  return {
    itemBeingDragged: monitor.getItem(),
    componentType: monitor.getItemType(),
    beingDragged: monitor.isDragging()
  };
})(
  class CustomDragLayer extends React.PureComponent {
    componentDidUpdate(prevProps) {
      dragPreviewRef = this.rootNode;
    }

    render() {
      if (!this.props.beingDragged) return null;
      return (
        <div
          role="presentation"
          ref={el => (this.rootNode = el)}
          style={{
            position: "fixed",
            pointerEvents: "none",
            top: 0,
            left: 0
          }}
        >
          {renderComponent(
            this.props.componentType,
            this.props.itemBeingDragged
          )}
        </div>
      );
    }
  }
);

@stellarhoof Terima kasih atas jawaban yang bagus! Sayangnya solusinya tidak berfungsi pada IE11 untuk saya. subscribeToOffsetChange sepertinya tidak memanggil callback yang kita berikan ke dalamnya. Untungnya, saya dapat memperbaikinya dengan tidak menggunakan subscribeToOffsetChange , tetapi hanya mengatur terjemahan di dalam kolektor seperti:

let dragLayerRef: HTMLElement = null;

const layerStyles: React.CSSProperties = {
  position: 'fixed',
  pointerEvents: 'none',
  zIndex: 100,
  left: 0,
  top: 0,
  width: '100%',
  height: '100%',
};

const dragLayerCollector = (monitor: DragLayerMonitor) => {
  if (dragLayerRef) {
    const offset = monitor.getSourceClientOffset() || monitor.getInitialClientOffset();

    if (offset) {
      dragLayerRef.style["transform"] = `translate(${offset.x}px, ${offset.y}px)`;
    } else {
      dragLayerRef.style["display"] = `none`;
    }
  }

  return {
    item: monitor.getItem(),
    isDragging: monitor.isDragging(),
  };
};

export default DragLayer(dragLayerCollector)(
  (props): JSX.Element => {
    if (!props.isDragging) {
      return null;
    }

    return (
      <div style={layerStyles}>
        <div ref={ (ref) => dragLayerRef = ref }>test</div>
      </div>
    );
  }
);

@stellarhoof saya perhatikan renderComponent tidak ditentukan. Apakah ini bagian dari file yang lebih besar? (React juga tidak diimpor)

@choffmeister Sudahkah Anda memperbarui versi dnd yang lebih baru? Tampaknya perubahan pada konteks telah merusak implementasi Anda dan saya ingin mencobanya dan membandingkan dengan apa yang dilakukan @stellarhoof

@stellarhoof Terima kasih atas jawaban yang bagus! Sayangnya solusinya tidak berfungsi pada IE11 untuk saya. subscribeToOffsetChange sepertinya tidak memanggil callback yang kita berikan ke dalamnya. Untungnya, saya dapat memperbaikinya dengan tidak menggunakan subscribeToOffsetChange , tetapi hanya mengatur terjemahan di dalam kolektor seperti:

let dragLayerRef: HTMLElement = null;

const layerStyles: React.CSSProperties = {
  position: 'fixed',
  pointerEvents: 'none',
  zIndex: 100,
  left: 0,
  top: 0,
  width: '100%',
  height: '100%',
};

const dragLayerCollector = (monitor: DragLayerMonitor) => {
  if (dragLayerRef) {
    const offset = monitor.getSourceClientOffset() || monitor.getInitialClientOffset();

    if (offset) {
      dragLayerRef.style["transform"] = `translate(${offset.x}px, ${offset.y}px)`;
    } else {
      dragLayerRef.style["display"] = `none`;
    }
  }

  return {
    item: monitor.getItem(),
    isDragging: monitor.isDragging(),
  };
};

export default DragLayer(dragLayerCollector)(
  (props): JSX.Element => {
    if (!props.isDragging) {
      return null;
    }

    return (
      <div style={layerStyles}>
        <div ref={ (ref) => dragLayerRef = ref }>test</div>
      </div>
    );
  }
);

Ini berhasil untuk saya!
Saya menggunakan versi ini: 9.4.0

Solusi lain tampaknya hanya berfungsi untuk versi yang lebih lama.

Hai teman-teman,

Jangan mencoba membuat komponen animasi css dapat diseret, atau setidaknya hapus properti transisi sebelum melanjutkan.

Setelah menghapus properti transisi onStart dan menambahkannya kembali diEnd, semuanya bekerja dengan baik

Untuk orang yang menggunakan kait (useDragLayer hook) dan mendarat di sini: Saya membuka tiket khusus tentang implementasi hook, dengan proposal solusi di sini: https://github.com/react-dnd/react-dnd/issues/2414

Apakah halaman ini membantu?
0 / 5 - 0 peringkat