React-dnd: CustomDragLayer أداء بطيء

تم إنشاؤها على ٧ ديسمبر ٢٠١٦  ·  29تعليقات  ·  مصدر: react-dnd/react-dnd

هل هناك أي سبب يجعل CustomDragLayer لي أداءً سيئًا؟ إنه يشبه وجود إطارات منخفضة في الثانية ، أو بالأحرى ، الكائن التعسفي الذي أسحبه متأخرًا ولا يبدو سلسًا

التعليق الأكثر فائدة

يبدو أن رد فعل-dnd-html5-backend يتمتع بأداء رهيب مع DragLayer المخصص في حين أن رد الفعل-dnd-touch-backend يتمتع بأداء جيد.

ال 29 كومينتر

لقد اختبرت هذا أيضًا. تكمن المشكلة هنا في أنه حتى إذا قمت بتصيير مكونات نقية لا تتغير في طبقة السحب ، فإن تغيير الإزاحة يؤدي على الأقل إلى تسوية رد الفعل في كل حركة ماوس واحدة. هذا غير فعال للغاية إذا كان لدى المرء الحالة الشائعة ، حيث يجب أن تعرض طبقة السحب عنصر معاينة مخصص غير متغير.

للتغلب على ذلك ، قمت بتكرار DragLayer.js الذي يقوم بعمل الإزاحة بالنسبة لي وهذا بطريقة عالية الأداء ، مما يعني تغيير نمط الحاوية التي تتحرك حول المعاينة مباشرة.

// 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);
  };
}

يمكن بعد ذلك استخدام هذا بالطريقة التالية:

// 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)

تحسين الأداء ملحوظ جدًا:

drag-layer-default

drag-layer-performant

بالطبع لا يزال DragLayer الافتراضي أكثر مرونة. التنفيذ الأكثر أداءً هو أسرع ، لأنه يتعامل مع حالة خاصة. لكن أعتقد أن هذه الحالة الخاصة شائعة جدًا.

gaearon هل هذا التنفيذ المتخصص ولكن الأكثر كفاءة مثيرًا للاهتمام بالنسبة لك ليتم دمجه كبديل في رد فعل dnd؟

تضمين التغريدة شكرا لمشاركتك. لقد ألقيت نظرة سريعة عليها قبل يوم واحد ولكني نظرت إليها بعمق فقط في ذلك الوقت. يبدو أن الكود الذي نشرته متخصص جدًا في عملية التنفيذ ، فهل يمكنك الإشارة إلى الجوانب المهمة اللازمة لذلك؟ وكيف يتم استخدام CustomDragLayerRaw سيكون مفيدًا جدًا أيضًا.

لست متأكدًا مما إذا كانت كل هذه المكونات الإضافية والمكتبات والأكواد الأخرى التي لديك جزء من الحل أم لا.

شكرا

لقد جربت وتمكنت من إنجاحها. يبدو أن المحتوى الذي تريده في "سحب الشيء الشبح" يجب أن يكون في قسم "الإزاحة". لا أعرف الطريقة المثلى للقيام بذلك ، لكن في الاختبار الذي أجريته ، قمت فقط بوضع div مشابه للمحتوى المسحوب. لم أجعل عنصر طبقة السحب هو نفسه العنصر المسحوب ، لكن تمرير بعض البيانات وتغيير حالة عنصر طبقة السحب ليس بالأمر الصعب جدًا.

الأداء جيد جدًا ، على الرغم من أنه لا يعمل إلا عند السحب خارج الحاوية. إذا قمت بالسحب في دوائر حول العنصر ، فسيكون الأداء أيضًا لا تشوبه شائبة مثل الحالة السابقة (على سبيل المثال ، لديك قائمة من 4 عناصر ، تسحب الماوس وتحتفظ به في منطقة العنصر الرابع). ومع ذلك ، إذا قمت برسم دوائر باستخدام الفأرة ، وسحبها عبر جميع العناصر الأربعة ، فستظل متأخرة ، كما كان من قبل ، ولكن مع تحسن طفيف في الثانية (مع ذلك لا يزال بطيئًا). لكن هذا التنفيذ هو بداية جيدة. على الأقل في معظم الحالات الشائعة عندما تريد السحب من حاوية إلى أخرى ، فهذا بالتأكيد سيبقي تجربة المستخدم جيدة.

ماذا تقصد بالضبط بـ laggy:

  1. هل FPS منخفض بحيث يبدو متعثرا ، أو
  2. هل يتحرك العنصر بسلاسة ، ولكن أثناء التحرك يكون دائمًا مسافة ما وراء حركة الماوس؟

سأقول كليهما

عند الاستخدام الإضافي ، أدرك أن الحل لا يقضي حقًا على التأخير تمامًا. لدي بطاقة div تحتوي على بعض الميزات الهادئة. لقد قدم الحل الذي قدمته في البداية زيادة كبيرة في معدل الإطارات في الثانية مقارنةً بالمثال الموجود في المستندات ، ومع ذلك ، نظرًا لأنني أجعل div الخاص بي أكثر تعقيدًا ، فقد بدأ يتأخر كما لو لم يكن لدي هذا.

ليس لدي أي فكرة عما إذا كانت واجهة المستخدم الدلالية تسبب هذا أو تتفاعل. إذا كان هناك أي طريقة أخرى أفضل لجعل طبقة السحب عالية الأداء ، أود حقًا أن أعرف.

أيضًا إذا كان أي شخص يرغب في محاولة معرفة ما إذا كان يتأخر أيضًا بالنسبة له ، فقد استخدمت بطاقة واجهة مستخدم دلالية ، مع الكثير من العناصر بداخلها. تحتوي بطاقة العنصر النائب الخاص بي على عنوانين ، و 5 "ملصقات" دلالية ، و 3 "رموز" دلالية ، وصورة رمزية.

بعد إجراء مزيد من الاختبار باستخدام هذا المثال المختلف على: http://react-trello-board.web-pal.com/ (تطبيقه مشابه جدًا لما نشره choffmeister سابقًا) ، ما زلت أتأخر عند محاولة سحب ملف div المعقد الخاص بي . أنا سعيد لأن هذا هو الاستنتاج الذي توصلت إليه ، لأنني لست بحاجة إلى إجراء المزيد من التجارب مع الكود ؛ المشكلة تكمن في div الخاص بي ، والذي يمكن إصلاحه بسهولة.

ما زلت أعاني من هذا بشكل سيء جدًا باستخدام رمز مشابه جدًا للمثال الموجود في المستندات. يجب أن يكون حساب التحويل بعد كل حركة ماوس صغيرة؟

هل وجد أي شخص آخر حلا لهذا؟

لدي مشكلة في الأداء ، حتى مع PureComponents. يبدو أن استخدام ميزة "Highlight Updates" في المكون الإضافي Chrome React Dev للتشخيص كان يتم تحديث جميع مكوناتي عندما تم تغيير عنصر currentOffset . للتخفيف ، تأكدت من أن DragLayer الذي يمر على هذه الخاصية يحتوي فقط على معاينة السحب نفسها ، على الرغم من وجود DragLayer بالفعل في المستوى العلوي من التطبيق لتتبع العنصر الذي يتم سحبه.

لم أحاول تكييف حل choffmeister ولكن يبدو أنه سيحل المشكلة أيضًا. أعتقد أنه ربما ينبغي النظر في شيء من هذا القبيل للمكتبة الأساسية لأنني لا أستطيع إلا أن أتخيل أن هذه مشكلة شائعة لأي شخص يقوم بتنفيذ معاينات السحب.

لقد تأكدت من أن DragLayer الذي يمر على هذه الخاصية يحتوي فقط على معاينة السحب نفسها ، على الرغم من وجود DragLayer بالفعل في المستوى العلوي من التطبيق

لست متأكدا من أنني أتابع! لدي طبقة سحب مخصصة واحدة فقط في تطبيقي ، على ما أعتقد!

إنها مشكلة خاصة بحالة الاستخدام الخاصة بي - تم استخدام طبقة سحب بالفعل قبل الحاجة إلى معاينة سحب مخصصة ، من أجل تتبع العنصر الذي يتم سحبه. لقد اعتقدت في البداية أنه من المنطقي استخدام نفس طبقة السحب للقيام بمعاينة السحب المخصصة عندما أضفتها ، ولكن نظرًا لأن هذا كان في المستوى الأعلى من تطبيقي ، فإن تغيير الدعائم لهذا في كل حركة طفيفة تسبب في الكثير من تحديثات المكونات الإضافية.

مسكتك ، شكرا.

لقد قمت بتبسيط طبقة السحب الخاصة بي إلى مكون غبي واحد ، وبقدر ما أستطيع أن أقول إن حساب x / y الثابت هو الذي يسبب التأخر.

لا أتابع تمامًا ما يفعله choffmeister أعلاه حتى الآن ، لكن يبدو أنني سأضطر إلى إلقاء نظرة فاحصة.

gaearon يبدو أن هذه مشكلة جيدة جدًا بالنسبة إلى lib ، هل هناك أي اقتراحات بشأن الإصلاح المناسب؟

يبدو أن رد فعل-dnd-html5-backend يتمتع بأداء رهيب مع DragLayer المخصص في حين أن رد الفعل-dnd-touch-backend يتمتع بأداء جيد.

كندة واضحة لكنها تساعد:

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

حل آخر هو استخدام الحزمة react-throttle-render لضبط طريقة العرض الخاصة بـ DragLayer.

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

شكرًا @ aks427 سأحاول ذلك وأرى ما إذا كان يتحسن أكثر (باستخدام طبقة سحب مخصصة مثل أعلاه أيضًا والتي ساعدت كثيرًا في معظم الحالات ولكن ليس كلها)

dobesv تغيير رد فعل- dnd-touch-backend حل المشكلة. حاولت أيضًا عرض رد فعل خنق ، ولكن لا يزال يبدو غير جيد.

نعم ، أعلم أن الواجهة الخلفية تعمل باللمس ، لكنها لا تدعم عمليات السحب والإفلات لتحميل الملفات ، والتي أريد استخدامها.

dobesv نستخدم https://react-dropzone.js.org/ لسحب الملفات وإفلاتها :) ربما يكون ذلك مفيدًا لك أيضًا.

dobesv تجاهل تعليقاتي. رد فعل - Dropzone يدعم فقط إسقاط الملفات.

ولكن بالنسبة للسيناريو الأكثر شيوعًا ، إذا كان الملف بحاجة إلى تحميل ، فيجب أن يكون الملف خارج المتصفح. نحن بحاجة فقط إلى إسقاط الملفات يجب أن يكون على ما يرام.

نوع من عدم التفكير ، لكنني استخدمت إصلاح طبقة السحب المؤدي وقضيت
الكثير من الوقت في البحث في الكود الخاص بي للحد من عدد عمليات العرض التي كانت
يتم استدعاؤها بواسطة التطبيق (الكثير من تبديل المكون -> PureComponent) و
في النهاية ، لم أكن بحاجة حتى إلى استخدام عرض رد فعل-خنق-.

أنا بالتأكيد أوصي بالنظر في استخدام PureComponent ومحاولة ذلك
قم بتكبير الكود الخاص بك بدلاً من البحث عن حل سهل لهذا الموضوع إذا
تقديمك بطيء! سأقوم بنشر المزيد إذا قمنا بتحسين نموذج Performant في
رغم كل ذلك!

>

framerate حتى استخدام PureComponents ، إنها مشكلة لأن خوارزمية التسوية مكلفة للتشغيل عند كل حركة بالماوس. ما عليك سوى إنشاء ملف تعريف لتطبيقك باستخدام Chrome DevTools ووحدة المعالجة المركزية (CPU) التي تقلص إلى 4X ، وهو تباطؤ واقعي للأجهزة المحمولة متوسطة النهاية.

للأجيال القادمة وأي شخص آخر يعاني من أداء السحب ولا تريد سحب الكثير من التعليمات البرمجية كما في حل 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 شكرا لك على الإجابة الرائعة! للأسف الحل لا يعمل على IE11 بالنسبة لي. لا يبدو أن subscribeToOffsetChange يقوم باستدعاء رد الاتصال الذي نمرره إليه. لحسن الحظ ، تمكنت من إصلاحه من خلال عدم استخدام subscribeToOffsetChange ، ولكن بدلاً من ذلك ، قمت فقط بتعيين الترجمات داخل المجمع كما يلي:

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 لاحظت أن renderComponent غير معرّف. هل كان هذا جزءًا من ملف أكبر؟ (لا يتم استيراد React أيضًا)

choffmeister هل قمت بتحديث الإصدارات الأحدث من dnd؟ يبدو أن التغييرات التي تم إجراؤها على السياق قد كسرت عملية التنفيذ وأردت تجربتها ومقارنتها بما كان يفعله stellarhoof

stellarhoof شكرا لك على الإجابة الرائعة! للأسف الحل لا يعمل على IE11 بالنسبة لي. لا يبدو أن subscribeToOffsetChange يقوم باستدعاء رد الاتصال الذي نمرره إليه. لحسن الحظ ، تمكنت من إصلاحه من خلال عدم استخدام subscribeToOffsetChange ، ولكن بدلاً من ذلك ، قمت فقط بتعيين الترجمات داخل المجمع كما يلي:

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>
    );
  }
);

لقد نجح هذا بالنسبة لي!
أنا أستخدم هذا الإصدار: 9.4.0

يبدو أن الحلول الأخرى تعمل فقط مع الإصدارات القديمة.

مرحبا شباب،

لا تحاول جعل مكون متحرك css قابل للسحب ، أو على الأقل إزالة خاصية النقل قبل المتابعة.

بعد إزالة خاصية الانتقال onStart وإضافتها مرة أخرى onEnd ، كل شيء يعمل بشكل صحيح

بالنسبة للأشخاص الذين يستخدمون الخطافات (خطاف useDragLayer) ويهبطون هنا: فتحت تذكرة خاصة بتنفيذ الخطاف ، مع اقتراح حل بديل هنا: https://github.com/react-dnd/react-dnd/issues/2414

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات