Seseorang pada akhirnya akan bertanya! API kait baru mungkin bisa membantu di sini. Saya pikir sebagian besar API bisa tetap sama, dengan pemetaan HOC langsung ke hook.
Saya bertanya-tanya apakah kita dapat mengganti connectDragSource
dan connectDropTarget
hanya dengan meneruskan nilai useRef
. Itu pasti bisa membuat barang lebih bersih jika itu memungkinkan!
Saya tidak sabar untuk mulai menggunakan pengait di perpustakaan ini. Segera setelah jenis dibuat untuk alfa, kita dapat menyiapkan cabang migrasi
Saya bermain-main di cabang: percobaan / kait, hanya untuk memikirkan bagaimana API ini akan terlihat. Komponen BoardSquare terlihat seperti ini:
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>,
)
}
Jadi idenya di sini adalah bahwa createDropTarget
menyiapkan pengetahuan logis tentang item drag / drop, ID dan predikasinya, dan hook useDnd akan menyambungkannya ke sistem DnD dan mengumpulkan props.
Perhatikan bahwa ini hanya membuat sketsa desain kandidat, itu tidak benar-benar diterapkan
@darthtrevino Cabang mana yang Anda kerjakan? Saya bertanya-tanya apakah kita bisa menghapus connectDropTarget
semua bersama-sama menggunakan referensi. Saya ingin melihat apakah saya bisa membuatnya berfungsi di cabang Anda!
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 Saya rasa saya telah melihat API berbasis hook serupa di mana ref disediakan dan dikembalikan oleh hook itu sendiri (seperti const [isOver, canDrop, ref] = useDnd(...)
), siap untuk komponen konsumsi ditempatkan di pohon JSX-nya)
Saya kira itu berhasil. Itu membuat lebih sulit untuk menggunakan referensi dalam beberapa kait, tetapi tidak ada yang menghentikan Anda menulis sesuatu yang menggabungkan beberapa referensi menjadi satu referensi. Perpustakaan apa ini?
Sepertinya kita harus melihat konvensi tentang ini!
Ini membuat lebih sulit untuk menggunakan referensi dalam banyak kait, tetapi tidak ada yang menghentikan Anda menulis sesuatu yang menggabungkan beberapa referensi menjadi satu referensi
Benar, dan benar :-)
Perpustakaan apa ini?
Sepertinya tidak dapat menemukannya lagi atm: - / - banyak percobaan seputar kait dalam dua minggu terakhir ...
Ada https://github.com/beizhedenglong/react-hooks-lib yang melakukannya
const { hovered, bind } = useHover(); return <div {...bind}>{hovered ? 'yes' : 'no'}</div>;
yang saya kira berarti bind
termasuk referensi?
[edit: tidak, itu hanya mencakup { onMouseEnter, onMouseLeave }
, tentu saja ...]
Tapi saya ingat melihat beberapa API lain mengembalikan ref langsung dari hook.
Tidak banyak di sana, dan tidak sedang dibangun saat ini, tetapi cabang tempat saya berada adalah experiment/hooks
Hanya mengulang di sini:
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>,
)
}
Ini adalah cara kerja rangkaian dragSource & dropTarget, jika kita menggunakan ref sebagai argumen pertama, argumen lainnya dapat menghubungkannya ke beberapa konsep 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>
)
}
Jadi useDnd akan terlihat seperti
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)
}
Ide gila, bagaimana jika kita tidak peduli dengan connect
dan 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' }} />
}
Saya pikir Anda dapat menyingkirkan koneksi, tetapi saya tidak yakin tentang monitor, di situlah Anda mendapatkan alat peraga seperti isDragging from
jadi mungkin useDragSource(ref, <type>, <spec>, <collect>)
. Itu argumen yang banyak, dan mungkin aneh untuk memiliki dua benda gemuk di samping satu sama lain
Bisakah kita mengembalikan semua props dari monitor?
Mungkin, saya pikir ini adalah satu-satunya metode yang akan menyebabkan masalah: https://github.com/react-dnd/react-dnd/blob/84db06edcb94ab3dbb37e8fe89fcad55b1ad07ce/packages/react-dnd/src/interfaces.ts#L117
IIRC, DragSourceMonitor, DropTragetMonitor, dan DragLayerMonitor semuanya turun ke kelas DragDropMonitor. Jadi saya tidak berpikir kita akan mengalami tabrakan penamaan, tapi saya akan memeriksa ulang itu.
@yched Hanya bermain-main dengan ini dan kait dan perhatikan bahwa kita harus meneruskan wasit. Lihat kasus ketika sesuatu merupakan sumber dan target,
const Test = () => {
const ref = useRef(null)
const source = useDragSource(ref, …props)
const target = useDragTarget(ref, …props)
return <div ref={ref}>…</div>
}
@ jacobp100 memang, masuk akal.
Oke, ide,
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>
}
Perhatikan bahwa spesifikasi untuk sumber dan target seret tidak menerima parameter apa pun, karena Anda sudah memiliki akses ke sana
useMonitorSubscription
dapat melakukan hal yang sama pada objek untuk mengurangi pembaruan
Saya telah melihat awal di sini . Tidak mendapat tes, tetapi contoh catur bekerja dengan kait - harus menunjukkan apa yang ingin saya lakukan!
Saya pikir memiliki useDragSource(ref, <type>, <spec>, <collect>)
adalah proposal yang bekerja sangat bagus dan tidak membawa banyak perubahan api. Satu-satunya perbedaan adalah Anda berubah dari hoc menjadi hook.
Juga memiliki dua benda gemuk di samping satu sama lain bukanlah masalah besar menurut saya karena Anda juga harus melakukannya sebelumnya:
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>;
};
Hal yang menyenangkan adalah Anda dapat menggunakan nilai dari hook lain juga.
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>;
};
Satu keuntungan bagus adalah kita bisa menghapus props dan komponen dari argumen beginDrag dan semua fungsi lainnya menerima karena Anda sudah memiliki akses ke mereka di lingkup.
^ Saya baru saja memperbarui komentar terakhir saya untuk menunjukkan bahwa collectSource
tidak meneruskan fungsi di monitor - Anda baru saja membaca dari ruang lingkup
@ jobp100 Saya mengerti. Bagi saya, pertanyaannya adalah apakah kita memerlukan pengait lain untuk mengumpulkan data dari monitor atau apakah kita bisa menerapkannya di useDragSource dan useDropTarget juga.
Masuk akal ketika barang-barang itu HOC, di mana Anda harus menghubungkan barang-barang yang terhubung.
Tapi sekarang tidak ada persyaratan teknis untuk memasangkannya lagi, jadi saya biarkan terpisah.
Kemudian Anda memiliki lebih banyak kebebasan dengannya - mungkin satu atau lebih komponen turunan untuk merespons perubahan monitor, tetapi bukan komponen yang mulai diseret. Ini juga memiliki bonus tambahan bahwa jika Anda tidak menggunakan langganan monitor, hook itu dapat terguncang.
Meskipun demikian, ini adalah draf awal! Saya tidak menentang untuk mengubahnya jika itu adalah konsensus!
Itu argumen yang bagus. Satu-satunya masalah yang saya lihat adalah pengguna dapat menjadi bingung ketika mereka memanggil monitor secara langsung dan bertanya-tanya mengapa monitor tidak berfungsi dengan benar:
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>
}
Mungkin itu bisa diselesaikan dengan dokumentasi dan peringatan ketika Anda memanggil fungsi di luar hook useMonitor
.
Sebenarnya, itu akan berhasil! Ini salah satu hal yang tidak 100% saya sukai: callback dalam useMonitor
digunakan baik untuk deteksi perubahan dan nilai pengembalian. Rasanya seperti melawan hook saat ini di inti React.
Mungkin sesuatu seperti ini bekerja lebih baik,
const Test = (props) => {
...
useMonitorUpdates(targetMonitor, () => [targetMonitor.isDragging()]);
return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}
Memang, jauh lebih mudah untuk memperkenalkan bug dengan formulir ini
Saya tidak 100% yakin dengan internal react-dnd tetapi bukankah monitornya ada di sana sehingga kita tidak perlu membuat komponen setiap kali mouse Anda bergerak?
Jadi yang sebelumnya akan berhenti berfungsi jika Anda menghapus useMonitorSubscription dan hanya memiliki monitor.isDragging()
dalam fungsi render?
Karena itu, ini tidak akan berfungsi dengan benar?
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>
}
Monitor memiliki metode subscribe
yang memberi tahu pendengarnya setiap kali ada nilai yang diperbarui. Jadi kita perlu melakukan _something_ agar komponen tahu kapan harus mengupdate.
Memperluas posting sebelumnya, jika kita membuat deteksi perubahan pengoptimalan sebagai fitur opsional, ini bisa sesederhana,
const Test = (props) => {
...
useMonitorUpdates(sourceMonitor);
return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}
Beberapa ide.
Pertama, dapatkah kita membuat argumen ref
opsional dengan meminta hook mengembalikan implementasi 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);
Kedua, daripada membuat mereka memanggil pengait lain di useMonitorUpdates
, saya bertanya-tanya apakah kita bisa melakukan hal berikut:
const dragSource = useDragSource('item', spec);
const { isDragging } = dragSource.subscribe(() => ({
isDragging: targetMonitor.isDragging()
}));
Saya akan menutup ini untuk saat ini, karena ada API kandidat. Jangan ragu untuk mengomentarinya dengan masalah baru. Terima kasih!
Sepertinya API kait memiliki cacat dalam desain: https://github.com/Swizec/useDimensions/issues/3
Menarik, jadi saya kira alternatifnya adalah kami menggunakan fungsi koneksi, seperti yang saat ini kami lakukan di API berbasis kelas:
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>
)
}
Jadi secara umum, API akan terlihat seperti ...
const [props, connectDragSource, connectDragPreview] = useDrag(spec)
const [props, connectDropTarget] = useDrop(spec)
Saya agak berharap untuk tidak memerlukan fungsi konektor, tetapi jika API rusak tanpanya maka kita dapat melakukannya
Ketika saya membaca masalah itu - API kami serupa, tetapi saya pikir masalahnya adalah mereka menggunakan efek tata letak untuk mendapatkan pengukuran simpul DOM. Kami tidak benar-benar menggunakan efek tata letak di sini, hanya mendaftarkan simpul DOM dengan dnd-core.
@gaearon , API hook yang kami usulkan sangat mirip dengan API useDimensions - apakah bentuk spesifik itu antipattern (misalnya let [props, ref] = useCustomHook(config)
), atau apakah itu istimewa untuk masalah yang coba dipecahkan oleh perpustakaan?
@darthtrevino Sejauh yang saya mengerti jika Anda menggunakan hook useDragSource dan Anda meneruskan ref ke komponen anak dan rendering komponen anak, kami tidak akan memperbarui node dom terdaftar di dnd-core:
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>
);
}
Yech. Saya akan melihat apakah saya dapat membuat kasus uji untuk ini akhir minggu ini untuk membuktikan apakah itu meledak atau tidak
Jika meledak, menggunakan fungsi konektor akan menjadi fallback
Jika saya melakukan semuanya dengan benar, saya dapat mereproduksinya di sini: https://codesandbox.io/s/xj7k9x4vp4
Kaboom, kerja bagus, terima kasih @ k15a . Saya akan memperbarui API hooks untuk segera menggunakan fungsi koneksi dan menyertakan contoh Anda sebagai kasus uji
Jadi saya menghabiskan banyak waktu tadi malam mengerjakan ulang API kait untuk menggunakan fungsi konektor. Sejauh desain API berjalan, saya benar-benar membencinya.
Pikiran saya selanjutnya adalah kita bisa mengembalikan callback-ref daripada objek ref. Ini akan memberi kita fleksibilitas untuk digunakan sebagai wasit secara langsung atau untuk memberikan wasit ke dalamnya
Menggunakan Langsung:
let [props, dragSource] = useDrag({spec}) // dragSource result is a function
return <div ref={dragSource} {...props}>hey</div>
Merantai
let [dragProps, dragSource] = useDrag({spec})
let [dropProps, dropTarget] = useDrag({spec})
return <div ref={node => dragSource(dropTarget(node))}>hey</div>
Dengan Objek Ref
let ref = useRef(null)
let [dragProps, dragSource] = useDrag({spec})
let [dropProps, dropTarget] = useDrag({spec})
dragSource(dropTarget(ref))
return <div ref={ref}>hey</div>
Bagi saya, rasanya useLayoutEffect
harus diaktifkan jika komponen atau turunannya diperbarui. Jika ya, kita bisa menggunakan itu.
Saya telah membuat tiket di repo react . Jangan ragu untuk berkomentar.
let [dragProps, dragSource] = useDrag({spec})
let [dropProps, dropTarget] = useDrag({spec})
return <div ref={node => dragSource(dropTarget(node))}>hey</div>
Saya tidak tahu seberapa bagus ini bekerja karena Anda akan memanggil wasit dengan setiap render. Jadi dengan setiap render, Anda perlu menghubungkan dan memutuskan sambungan.
Juga bukankah lebih baik untuk memilikinya
node => {
dragSource(node)
dropTarget(node)
}
Itu akan menjadi hal yang sama
Jadi, untuk memudahkan komentar saya sebelumnya, API di # 1280 sedang dibentuk menjadi lebih baik dari yang saya pikirkan sebelumnya. Jangan ragu untuk berkomentar di sini atau di sana