React-dnd: مناقشة هوكس API

تم إنشاؤها على ٢٦ أكتوبر ٢٠١٨  ·  43تعليقات  ·  مصدر: react-dnd/react-dnd

شخص ما كان سيسأل في النهاية! يمكن أن تساعد واجهة برمجة تطبيقات Hooks الجديدة هنا. أعتقد أن معظم واجهة برمجة التطبيقات يمكن أن تظل كما هي إلى حد كبير ، مع تعيين HOC مباشرة في الخطاف.

أتساءل عما إذا كان بإمكاننا استبدال connectDragSource و connectDropTarget بمجرد تمرير قيمة useRef . يمكن بالتأكيد جعل الأشياء أكثر نظافة إذا كان ذلك ممكنًا!

design decisions discussion

ال 43 كومينتر

لا أطيق الانتظار لبدء استخدام الخطافات في هذه المكتبة. بمجرد أن يتم خبز الأنواع لـ alpha ، يمكننا إنشاء فرع ترحيل

أنا ألعب في فرع: تجربة / خطافات ، فقط للتفكير في الشكل الذي قد تبدو عليه واجهة برمجة التطبيقات. يبدو مكون BoardSquare كما يلي:


const dropTarget = createDropTarget(ItemTypes.KNIGHT, {
    canDrop: (props: BoardSquareProps) => canMoveKnight(props.x, props.y),
    drop: (props: BoardSquareProps) => moveKnight(props.x, props.y),
})

const BoardSquare = ({ x, y, children }) => {
    const black = (x + y) % 2 === 1
    const [connectDropTarget, isOver, canDrop] = useDnd(
        dropTarget,
        connect => connect.dropTarget,
        (connect, monitor) => !!monitor.isOver,
        (connect, monitor) => !!monitor.canDrop,
    )

    return connectDropTarget(
        <div>
            <Square black={black}>{children}</Square>
            {isOver && !canDrop && <Overlay color={'red'} />}
            {!isOver && canDrop && <Overlay color={'yellow'} />}
            {isOver && canDrop && <Overlay color={'green'} />}
        </div>,
    )
}

لذا فإن الفكرة هنا هي أن يقوم createDropTarget بإعداد المعرفة المنطقية حول عنصر السحب / الإفلات ، والمعرف الخاص به والتنبؤ به ، وربطه باستخدام خطاف useDnd في نظام DnD وجمع الدعائم.

لاحظ أن هذا مجرد رسم تخطيطي لتصميم مرشح ، ولم يتم تنفيذه بالفعل

darthtrevino ما هو الفرع الذي تعمل عليه؟ أتساءل عما إذا كان بإمكاننا إزالة connectDropTarget معًا باستخدام المراجع. أرغب في معرفة ما إذا كان بإمكاني تشغيله في فرعك!

const dropTarget = createDropTarget(ItemTypes.KNIGHT, {
    canDrop: (props: BoardSquareProps) => canMoveKnight(props.x, props.y),
    drop: (props: BoardSquareProps) => moveKnight(props.x, props.y),
})

const BoardSquare = ({ x, y, children }) => {
    const dropTargetElement = useRef(null);
    const black = (x + y) % 2 === 1
    const [isOver, canDrop] = useDnd(
        dropTargetElement,
        dropTarget,
        ...
    )

    return (
        <div ref={dropTargetElement}>
            <Square black={black}>{children}</Square>
            {isOver && !canDrop && <Overlay color={'red'} />}
            {!isOver && canDrop && <Overlay color={'yellow'} />}
            {isOver && canDrop && <Overlay color={'green'} />}
        </div>
    )
}

@ jacobp100 أعتقد أنني أبدو مشابهة لواجهات برمجة التطبيقات القائمة على الخطاف حيث يتم توفير المرجع وإعادته بواسطة الخطاف نفسه (مثل const [isOver, canDrop, ref] = useDnd(...) ) ، جاهز لوضع المكون المستهلك في شجرة JSX الخاصة به)

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

أعتقد أننا يجب أن نرى ما هي الاتفاقية على هذا!

يجعل من الصعب استخدام المرجع في عدة خطافات ، ولكن لا يوجد ما يمنعك من كتابة شيء يجمع عدة مراجع في مرجع واحد

صواب وصحيح :-)

ما هي هذه المكتبة؟

لا يمكن العثور عليه مرة أخرى: - / - الكثير من التجارب حول الخطافات في الأسبوعين الماضيين ...

هناك https://github.com/beizhedenglong/react-hooks-lib يفعل ذلك
const { hovered, bind } = useHover(); return <div {...bind}>{hovered ? 'yes' : 'no'}</div>;
الذي أعتقد أنه يعني bind يتضمن المرجع؟
[تحرير: كلا ، إنه يتضمن فقط { onMouseEnter, onMouseLeave } ، بالطبع ...]

لكنني أتذكر رؤية بعض واجهة برمجة التطبيقات (API) الأخرى تعيد المرجع مباشرة من الخطاف.

لا يوجد الكثير هناك ، ولا يتم إنشاءه حاليًا ، لكن الفرع الذي أعمل فيه هو experiment/hooks

مجرد تكرار هنا:

const dropTarget = createDropTarget(ItemTypes.KNIGHT, {
    canDrop: props => canMoveKnight(props.x, props.y),
    drop: props => moveKnight(props.x, props.y),
})

const BoardSquare = ({ x, y, children }) => {
    const black = (x + y) % 2 === 1
        const ref = useRef(null)
    const [isOver, canDrop] = useDnd(
        connect => connect(ref, dropTarget),
        monitor => monitor.isOver,
        monitor => monitor.canDrop,
    )

    return (
        <div ref={ref}>
            <Square black={black}>{children}</Square>
            {isOver && !canDrop && <Overlay color={'red'} />}
            {!isOver && canDrop && <Overlay color={'yellow'} />}
            {isOver && canDrop && <Overlay color={'green'} />}
        </div>,
    )
}

هذه هي الطريقة التي قد يعمل بها تسلسل dragSource & dropTarget ، إذا استخدمنا المرجع باعتباره الوسيطة الأولى ، فيمكن للوسيطات المتبقية توصيله بمفاهيم dnd المتعددة.

const dropTarget = createDropTarget(ItemTypes.CARD, {
    canDrop: () => false
    hover(props, monitor) {
        /**/
    },
})
const dragSource = createDragSource(ItemTypes.CARD, {
    beginDrag: props => /*...*/,
    endDrag: (props, monitor) => /*...*/
})

function Card({ text }) {
    const ref = useRef(null)
    const [isDragging] = useDnd(
        connect => connect(ref, dropTarget, dragSource),
        monitor => monitor.isDragging,
    )
    const opacity = isDragging ? 0 : 1

    return (
        <div ref={ref} style={{ ...style, opacity }}>
            {text}
        </div>
    )
}

لذلك سيبدو useDnd


export type DndConcept = DragSource | DropTarget
export type ConnectorFunction = (connector: Connector /*new type*/) => void
export type CollectorFunction<T> = (monitor: DragDropMonitor) => T

export function useDnd(
    connector: ConnectorFunction,
    ...collectors: CollectorFunction[]
): any[] {
    const dragDropManager = useDragDropManager()
        // magic
       return collectedProperties
}

export function useDragDropManager(): DragDropManager {
    return useContext(DragDropContextType)
}

فكرة مجنونة ، ماذا لو لم نتعب نفسك بـ connect و monitor ؟

const Test = props => {
  const ref = useRef(null);
  const { isDragging } = useDragSource(ref, ItemTypes.CARD, {
    canDrag: props.enabled,
    beginDrag: () => ({ id: props.id }),
  });

  return <div ref={ref} style={{ color: isDragging ? 'red' : 'black' }} />
}

أعتقد أنه يمكنك التخلص من الاتصال ، لكنني لست متأكدًا من الشاشة ، حيث تحصل على أدواتك مثل السحب من

لذلك ربما useDragSource(ref, <type>, <spec>, <collect>) . هذا كثير من الحجج ، وقد يكون من الغريب وجود جسمين سمينين بجانب بعضهما البعض

هل يمكننا فقط إرجاع جميع الدعائم من الشاشة؟

ربما ، أعتقد أن هذه هي الطريقة الوحيدة التي قد تسبب مشكلة: https://github.com/react-dnd/react-dnd/blob/84db06edcb94ab3dbb37e8fe89fcad55b1ad07ce/packages/react-dnd/src/interfaces.ts#L117

IIRC و DragSourceMonitor و DropTragetMonitor و DragLayerMonitor جميعها تنطلق إلى فئة DragDropMonitor. لذلك لا أعتقد أننا سنواجه اصطدامات تسمية ، لكنني سأفحص ذلك مرة أخرى.

yched مجرد اللعب مع هذا وخطافات ولاحظ أنه يتعين علينا تمرير المرجع. انظر الحالة عندما يكون هناك شيء ما هو مصدر وهدف ،

const Test = () => {
  const ref = useRef(null)
  const source = useDragSource(ref, …props)
  const target = useDragTarget(ref, …props)

  return <div ref={ref}>…</div>
}

@ jacobp100 حقا ، أمر منطقي.

حسنًا ، فكرة ،

const Test = (props) => {
  const ref = useRef(null)
  const sourceMonitor = useDragSource(ref, 'item' {
    beginDrag: () => ({ id: props.id })
  })
  const targetMonitor = useDropTarget(ref, ['item'] {
    drop: () => alert(targetMonitor.getItem().id)
  })
  const { isDragging } = useMonitorSubscription(targetMonitor, () => ({
    isDragging: targetMonitor.isDragging()
  })

  return <div ref={ref}>…</div>
}

لاحظ أن مواصفات مصدر السحب والهدف لا تتلقى أي معلمات ، نظرًا لأن لديك حق الوصول إليها بالفعل

useMonitorSubscription بعمل مساوٍ سطحي على الكائن لتقليل التحديثات

لقد ألقيت نظرة أولية هنا . لم يتم إجراء اختبارات ، لكن مثال الشطرنج يعمل مع الخطافات - يجب أن يُظهر ما أريد القيام به!

أعتقد أن وجود useDragSource(ref, <type>, <spec>, <collect>) هو الاقتراح الذي يعمل بشكل جيد حقًا ولا يؤدي إلى الكثير من التغييرات في واجهة برمجة التطبيقات. الاختلاف الوحيد هو أنك تتغير من مخصص إلى خطاف.

كما أن وجود جسمين سمينين بجانب بعضهما البعض ليس مشكلة كبيرة في رأيي لأنه كان عليك القيام بذلك من قبل أيضًا:

const DragDrop = () => {
  const ref = useRef(null);

  const dragSource = {
    beginDrag() {
      return props;
    }
  };

  const collectSource = monitor => {
    return {
      isDragging: monitor.isDragging()
    };
  };

  const { isDragging } = useDragSource(ref, "Item", dragSource, collectSource);

  const dropTarget = {
    drop() {
      return undefined;
    }
  };

  const collectTarget = monitor => {
    return {
      isOver: monitor.isOver()
    };
  };

  const { isOver } = useDropTarget(ref, "Item", dropTarget, collectTarget);

  return <div ref={ref}>Drag me</div>;
};

الشيء الجميل هو أنه يمكنك استخدام القيم من الخطافات الأخرى أيضًا.

const Drag = () => {
  const ref = useRef(null);
  const context = useContext(Context)

  const dragSource = {
    beginDrag() {
      context.setDragItem(props)
      return props;
    },
    endDrag() {
      context.setDragItem(null)
    }
  };

  const collectSource = monitor => {
    return {
      isDragging: monitor.isDragging()
    };
  };

  const { isDragging } = useDragSource(ref, "Item", dragSource, collectSource);

  return <div ref={ref}>Drag me</div>;
};

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

^ لقد قمت للتو بتحديث تعليقي الأخير لإظهار أن collectSource لا يمر في الشاشة إلى الوظيفة - لقد قرأت للتو من النطاق

@ jacobp100 فهمت. بالنسبة لي ، سيكون السؤال هو ما إذا كنا بحاجة إلى أداة ربط أخرى لجمع البيانات من الشاشة أو إذا كان بإمكاننا تنفيذها في useDragSource و useDropTarget أيضًا.

كان من المنطقي عندما كانت الأشياء ذات ترتيب أعلى ، حيث كان عليك ربط أشياء التوصيل على أي حال.

ولكن ليس هناك الآن أي متطلبات فنية لربطهما بعد الآن ، لذلك تركتهما منفصلين.

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

ومع ذلك ، هذه مسودة أولية! أنا لا أعارض تغييره إذا كان هذا هو الإجماع!

هذه حجة جيدة. المشكلة الوحيدة التي أراها هي أن المستخدم قد يشعر بالارتباك عندما يتصل بالشاشة مباشرة ويتساءل لماذا لا يتصرف بشكل صحيح:

const Test = (props) => {
  const ref = useRef(null)

  const sourceMonitor = useDragSource(ref, 'item', {
    beginDrag: () => ({ id: props.id })
  })

  const targetMonitor = useDropTarget(ref, ['item'], {
    drop: () => alert(targetMonitor.getItem().id)
  })

  const { isDragging } = useMonitor(targetMonitor, () => ({
    isDragging: targetMonitor.isDragging()
  })

  return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}

ربما يمكن حل ذلك من خلال الوثائق والتحذيرات عند استدعاء الوظيفة خارج الخطاف useMonitor .

في الواقع ، هذا سوف يعمل! إنه أحد الأشياء التي لست مهتمًا بها بنسبة 100٪: يتم استخدام رد الاتصال في useMonitor لكشف التغيير وقيمة الإرجاع. يبدو أنه يتعارض مع الخطافات الحالية في قلب React.

ربما يعمل شيء كهذا بشكل أفضل ،

const Test = (props) => {
  ...
  useMonitorUpdates(targetMonitor, () => [targetMonitor.isDragging()]);

  return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}

من المسلم به أنه من الأسهل بكثير إدخال الأخطاء باستخدام هذا النموذج

لست متأكدًا بنسبة 100٪ من العناصر الداخلية للتفاعل ، ولكن ليست الشاشة موجودة ، لذا لا يتعين علينا تقديم المكون في كل مرة يتحرك فيها الماوس؟

إذن سيتوقف السابق عن العمل إذا أزلت useMonitorSubscription وكان لديك monitor.isDragging() في وظيفة التقديم؟

لذلك لن يعمل هذا بشكل صحيح؟

const Test = (props) => {
  const ref = useRef(null)

  const sourceMonitor = useDragSource(ref, 'item', {
    beginDrag: () => ({ id: props.id })
  })

  return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}

تحتوي الشاشة على طريقة subscribe تُعلم المستمعين عند تحديث القيمة. لذلك نحن بحاجة إلى القيام بشيء ما حتى يعرف المكون وقت التحديث.

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

const Test = (props) => {
  ...
  useMonitorUpdates(sourceMonitor);

  return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}

بضعة أفكار.

أولاً ، هل يمكننا جعل الوسيطة ref اختيارية من خلال جعل الخطاف يعيد تنفيذ Ref ؟

const dragSource = useDragSource('item', spec);
return <div ref={dragSource}/>

// or if you want to use a ref
const ref = useRef();
const dragSource = useDragSource('item', dragSourceSpec)(ref); 
const dropTarget = useDropTarget('item', dropTargetSpec)(ref); 

ثانيًا ، بدلاً من جعلها تستدعي خطافًا آخر في useMonitorUpdates ، أتساءل عما إذا كان بإمكاننا القيام بما يلي:

const dragSource = useDragSource('item', spec);

const { isDragging } = dragSource.subscribe(() => ({
  isDragging: targetMonitor.isDragging()
}));

سأغلق هذا الآن ، نظرًا لوجود واجهة برمجة تطبيقات مرشح. لا تتردد في التعليق على ذلك بقضايا جديدة بالرغم من ذلك. شكر!

يبدو أن واجهة برمجة تطبيقات Hooks بها عيب في التصميم: https://github.com/Swizec/useDimensions/issues/3

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


const Box = ({ name }) => {
    const [{ isDragging }, dragSource] = useDrag({
        item: { name, type: ItemTypes.BOX },
        end: (dropResult?: { name: string }) => {
            if (dropResult) {
                alert(`You dropped ${item.name} into ${dropResult.name}!`)
            }
        },
        collect: monitor => ({
            isDragging: monitor.isDragging(),
        }),
    })
    const opacity = isDragging ? 0.4 : 1

    return (
        <div ref={node => dragSource(node)} style={{ ...style, opacity }}>
            {name}
        </div>
    )
}

بشكل عام ، ستبدو واجهة برمجة التطبيقات مثل ...

const [props, connectDragSource, connectDragPreview] = useDrag(spec)
const [props, connectDropTarget] = useDrop(spec)

كنت آمل نوعًا ما في الابتعاد عن طلب وظائف الموصل ، ولكن إذا تم كسر واجهة برمجة التطبيقات بدونها ، فيمكننا القيام بذلك

بينما قرأت هذه المشكلة - واجهات برمجة التطبيقات الخاصة بنا متشابهة ، لكنني أعتقد أن المشكلة هناك هي أنهم يستخدمون تأثير تخطيط للحصول على قياس عقدة DOM. نحن لا نستخدم تأثيرات التخطيط هنا ، فقط نقوم بتسجيل عُقد DOM مع نواة dnd.

gaearon ، واجهة برمجة تطبيقات hooks المقترحة مشابهة جدًا لواجهة برمجة التطبيقات useDimensions - هل هذا الشكل المحدد هو مضاد (على سبيل المثال let [props, ref] = useCustomHook(config) ) ، أم أنه خاص بالمشكلة التي تحاول المكتبة حلها؟

darthtrevino على حد

function Parent() {
  const ref = useRef();
  const dragSource = useDragSource(ref, ...);

  return <Child connect={ref} />;
}

function Child({ connect }) {
  const [open, setOpen] = useState(false);

  function toggle() {
    setOpen(!open);
  }

  return (
    <Fragment>
      <button onClick={toggle}>Toggle</button>
      {open ? <div ref={connect} /> : null}
    </Fragment>
  );
}

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

إذا تم تفجيره ، فسيكون استخدام وظائف الموصل هو الإجراء الاحتياطي

إذا فعلت كل شيء بشكل صحيح ، فقد تمكنت من إعادة إنتاجه هنا: https://codesandbox.io/s/xj7k9x4vp4

Kaboom ، عمل جيد ، شكرًا @ k15a . سوف أقوم بتحديث واجهة برمجة تطبيقات hooks لاستخدام وظائف الاتصال قريبًا وتضمين المثال الخاص بك كحالة اختبار

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

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

باستخدام مباشرة:

let [props, dragSource] = useDrag({spec}) // dragSource result is a function
return <div ref={dragSource} {...props}>hey</div>

تقييد

let [dragProps, dragSource] = useDrag({spec})
let [dropProps, dropTarget] = useDrag({spec})

return <div ref={node => dragSource(dropTarget(node))}>hey</div>

مع كائن المرجع

let ref = useRef(null)
let [dragProps, dragSource] = useDrag({spec})
let [dropProps, dropTarget] = useDrag({spec})
dragSource(dropTarget(ref))

return <div ref={ref}>hey</div>

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

لقد قمت بإنشاء تذكرة في رد الفعل الريبو . لا تتردد في التعليق.

let [dragProps, dragSource] = useDrag({spec})
let [dropProps, dropTarget] = useDrag({spec})

return <div ref={node => dragSource(dropTarget(node))}>hey</div>

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

أيضا لن يكون من الأفضل أن يكون ذلك

node => {
    dragSource(node)
    dropTarget(node)
}

سيكون نفس الشيء

لذا ، للتخفيف من تعليقي السابق ، فإن واجهة برمجة التطبيقات في # 1280 تتشكل لتكون أفضل مما كنت أعتقد في البداية. لا تتردد في التعليق هنا أو هناك

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