React-dnd: フックAPIディスカッション

作成日 2018年10月26日  ·  43コメント  ·  ソース: react-dnd/react-dnd

誰かが最終的に尋ねるつもりでした! 新しいフックAPIはおそらくここで役立つ可能性があります。 ほとんどのAPIは、HOCがフックに直接マッピングされているため、ほとんど同じままであると思います。

connectDragSourceconnectDropTargetuseRef値を渡すだけで置き換えることができるかどうか疑問に思います。 それが可能であれば、それは間違いなくものをよりきれいにすることができます!

design decisions discussion

全てのコメント43件

このライブラリでフックを使い始めるのが待ちきれません。 タイプがアルファ用にベイクされるとすぐに、移行ブランチを設定できます

このAPIがどのように見えるかを考えるために、実験/フックというブランチで遊んでいます。 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がドラッグアンドドロップアイテム、そのIDと予測に関する論理的知識を設定し、useDndフックがそれをDnDシステムに接続して、小道具を収集するというものです。

これは候補デザインをスケッチしただけであり、実際には実装されていないことに注意してください

@darthtrevinoどのブランチに取り組んでいますか? refを使用して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 refがフック自体によって提供および返され( const [isOver, canDrop, ref] = useDnd(...) )、消費コンポーネントをJSXツリーに配置する準備ができている同様のフックベースのAPIのように思われます)

私はそれがうまくいくと思います。 複数のフックで参照を使用するのは難しくなりますが、複数の参照を1つの参照に結合するものを書くのを止めることはできません。 これはどの図書館でしたか?

これに関する慣習が何であるかを見なければならないと思います!

複数のフックで参照を使用するのは難しくなりますが、複数の参照を1つの参照に結合するものを書くのを止めることはできません。

本当、そして本当:-)

これはどの図書館でしたか?

再びそれを見つけることができないようですatm:-/-過去2週間のフックの周りのたくさんの実験...

https://github.com/beizhedenglong/react-hooks-libがあり
const { hovered, bind } = useHover(); return <div {...bind}>{hovered ? 'yes' : 'no'}</div>;
bindに参照が含まれていると思いますか?
[編集:いいえ、もちろん{ onMouseEnter, onMouseLeave }だけが含まれています...]

しかし、他のAPIがフックから直接refを返すのを見たことを覚えています。

そこにはそれほど多くはなく、現時点では構築されていませんが、私がいるブランチは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のチェーンがどのように機能するかを示しています。最初の引数としてrefを使用すると、残りの引数で複数の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)
}

クレイジーなアイデア、 connectmonitor気にしないとどうなるでしょうか?

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' }} />
}

接続を取り除くことができると思いますが、モニターについてはよくわかりません。isDraggingfromのような小道具を入手できる場所です。

だから多分useDragSource(ref, <type>, <spec>, <collect>) 。 それは多くの議論であり、2つの太いオブジェクトが隣り合っているのは奇妙かもしれません

モニターからすべての小道具を返すことはできますか?

たぶん、これが問題を引き起こす唯一の方法だと思います: https

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>)を持つことは、非常にうまく機能し、APIの変更をあまりもたらさない提案だと思います。 唯一の違いは、ホックからフックに変更することです。

また、2つの太いオブジェクトを並べて配置することも、前に行う必要があったため、私の意見では大きな問題ではありません。

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

優れた利点の1つは、引数beginDragから小道具とコンポーネントを削除でき、スコープ内で既にアクセスできるため、他のすべての関数が受け入れることです。

^最後のコメントを更新して、 collectSourceがモニターから関数に渡されないことを示しました-スコープから読み取っただけです

@ jacobp100なるほど。 私にとって、問題は、モニターからデータを収集するために別のフックが必要かどうか、またはuseDragSourceとuseDropTargetにもそれを実装できるかどうかです。

とにかく接続のものをリンクしなければならなかったHOCであるとき、それは理にかなっています。

しかし、それらを結合するための技術的な要件はもうないので、私はそれらを別々のままにしました。

そうすれば、それらをより自由に使用できます。モニターの変更に応答する1つ以上の子コンポーネントがありますが、ドラッグを開始するコンポーネントはありません。 また、モニターサブスクリプションを使用していない場合、そのフックをツリーシェイクできるという追加のボーナスもあります。

そうは言っても、これは最初のドラフトです! それがコンセンサスであれば、私はそれを変更することに反対していません!

それは良い議論です。 私が見る唯一の問題は、ユーザーがモニターを直接呼び出すと混乱し、なぜ正しく動作しないのか疑問に思う可能性があることです。

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%熱心ではないことの1つです。 useMonitorのコールバックは、変更の検出と戻り値の両方に使用されます。 Reactコアの現在のフックに反しているように感じます。

たぶん、このようなものの方がうまくいくでしょう、

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

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

確かに、このフォームでバグを導入する方がはるかに簡単です

私はreact-dndの内部を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()
}));

APIの候補があるので、今はこれを閉じます。 ただし、新しい問題については、遠慮なくコメントしてください。 ありがとう!

フックAPIの設計に欠陥があるようです: https

興味深いので、別の方法は、現在クラスベースAPIで行っているように、接続関数を使用することだと思います。


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

したがって、一般的に、APIは次のようになります...

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

コネクタ関数を必要としないようにしたいと思っていましたが、コネクタ関数がないとAPIが壊れた場合は、それを実行できます。

私がその問題を読んだとき、私たちのAPIは似ていますが、DOMノードの測定値を取得するためにレイアウト効果を使用しているという問題があると思います。 ここでは実際にはレイアウト効果を使用しておらず、DOMノードをdnd-coreに登録するだけです。

@gaearon 、提案されたフックAPIはlet [props, ref] = useCustomHook(config) )ですか、それともライブラリが解決しようとしている問題に固有のものですか?

@darthtrevino useDragSourceフックを使用し、refを子コンポーネントに渡し、子コンポーネントが再レンダリングする場合、私が理解している限り、dnd-coreに登録されているdomノードは更新されません。

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

Kaboom、お疲れ様でした。

そのため、昨夜、コネクタ関数を使用するようにフックAPIを作り直すことに多くの時間を費やしました。 API設計に関する限り、私は本当にそれが嫌いです。

私の次の考えは、ref-objectの代わりにcallback-refを返すことができるということです。 これにより、参照として直接使用したり、参照を渡したりする柔軟性が得られます。

直接使用:

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が起動するように感じます。 もしそうなら、私たちはそれを使うことができます。

私はreactリポジトリでチケットを作成しました。 コメントしてください。

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

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

すべてのレンダリングでrefを呼び出すので、これがどれほどうまく機能するかはわかりません。 したがって、すべてのレンダリングで、接続と切断を行う必要があります。

また、そうする方が良いのではないでしょうか

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

それは同じことだろう

ですから、私の以前のコメントを簡単にするために、#1280のAPIは私が最初に思ったよりも良くなるように形作られています。 ここかそこにコメントしてください

このページは役に立ちましたか?
0 / 5 - 0 評価