Tôi đã triển khai react-dnd , một mixin kéo và thả HTML5 linh hoạt cho React với toàn quyền kiểm soát DOM.
Các thư viện kéo và thả hiện tại không phù hợp với trường hợp sử dụng của tôi nên tôi đã viết thư của riêng mình. Nó tương tự như đoạn mã chúng tôi đã chạy trong khoảng một năm trên Stampsy.com, nhưng được viết lại để tận dụng lợi thế của React và Flux.
Các yêu cầu chính mà tôi có:
- Không phát ra DOM hoặc CSS của riêng nó, để lại nó cho các thành phần tiêu thụ;
- Áp đặt cấu trúc càng ít càng tốt trên các thành phần tiêu thụ;
- Sử dụng kéo và thả HTML5 làm phần phụ trợ chính nhưng có thể thêm các phần phụ trợ khác nhau trong tương lai;
- Giống như API HTML5 ban đầu, nhấn mạnh việc kéo dữ liệu chứ không chỉ “các chế độ xem có thể kéo”;
- Ẩn các điều kỳ quặc API HTML5 khỏi mã tiêu thụ;
- Các thành phần khác nhau có thể là "nguồn kéo" hoặc "mục tiêu thả" cho các loại dữ liệu khác nhau;
- Cho phép một thành phần chứa nhiều nguồn kéo và thả mục tiêu khi cần thiết;
- Giúp mục tiêu thả dễ dàng thay đổi giao diện của chúng nếu dữ liệu tương thích đang được kéo hoặc di chuột;
- Giúp bạn dễ dàng sử dụng hình ảnh để kéo hình thu nhỏ thay vì ảnh chụp màn hình phần tử, tránh những điều kỳ quặc của trình duyệt.
Nếu những điều này nghe quen thuộc với bạn, hãy đọc tiếp.
Sử dụng
Nguồn kéo đơn giản
Đầu tiên, khai báo các loại dữ liệu có thể kéo được.
Chúng được sử dụng để kiểm tra "tính tương thích" của các nguồn kéo và mục tiêu thả:
// ItemTypes.js
module.exports = {
BLOCK: 'block',
IMAGE: 'image'
};
(Nếu bạn không có nhiều kiểu dữ liệu, thì libary này có thể không dành cho bạn.)
Sau đó, hãy tạo một thành phần có thể kéo rất đơn giản, khi được kéo, đại diện IMAGE
:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var Image = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
// Specify all supported types by calling registerType(type, { dragSource?, dropTarget? })
registerType(ItemTypes.IMAGE, {
// dragSource, when specified, is { beginDrag(), canDrag()?, endDrag(didDrop)? }
dragSource: {
// beginDrag should return { item, dragOrigin?, dragPreview?, dragEffect? }
beginDrag() {
return {
item: this.props.image
};
}
}
});
},
render() {
// {...this.dragSourceFor(ItemTypes.IMAGE)} will expand into
// { draggable: true, onDragStart: (handled by mixin), onDragEnd: (handled by mixin) }.
return (
<img src={this.props.image.url}
{...this.dragSourceFor(ItemTypes.IMAGE)} />
);
}
);
Bằng cách chỉ định configureDragDrop
, chúng tôi nóiDragDropMixin
hành vi kéo-thả của thành phần này. Cả hai thành phần có thể kéo và có thể kéo xuống đều sử dụng cùng một loại mixin.
Bên trong configureDragDrop
, chúng ta cần gọi registerType
cho từng tùy chỉnh của mình ItemTypes
mà thành phần hỗ trợ. Ví dụ: có thể có một số hình ảnh đại diện trong ứng dụng của bạn và mỗi hình ảnh sẽ cung cấp dragSource
choItemTypes.IMAGE
.
A dragSource
chỉ là một đối tượng chỉ định cách thức hoạt động của nguồn kéo. Bạn phải triển khai beginDrag
để trả về mục đại diện cho dữ liệu bạn đang kéo và một vài tùy chọn có thể điều chỉnh giao diện người dùng kéo. Bạn có thể tùy chọn triển khai canDrag
để cấm kéo hoặc endDrag(didDrop)
thực thi một số logic khi thả xảy ra (hoặc chưa). Và bạn có thể chia sẻ logic này giữa các thành phần bằng cách để một mixin được chia sẻ tạo ra dragSource
cho chúng.
Cuối cùng, bạn phải sử dụng {...this.dragSourceFor(itemType)}
trên một số (một hoặc nhiều) phần tử render
để đính kèm các trình xử lý kéo. Điều này có nghĩa là bạn có thể có nhiều “chốt kéo” trong một phần tử và chúng thậm chí có thể tương ứng với các loại mục khác nhau. (Nếu bạn không quen thuộc với Thuộc tính Spread JSX cú pháp của , hãy xem nó).
Mục tiêu thả đơn giản
Giả sử chúng tôi muốn ImageBlock
trở thành mục tiêu giảm giá cho IMAGE
s. Đó là khá nhiều giống nhau, ngoại trừ việc chúng ta cần phải đưa ra registerType
một dropTarget
thực hiện:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var ImageBlock = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
registerType(ItemTypes.IMAGE, {
// dropTarget, when specified, is { acceptDrop(item)?, enter(item)?, over(item)?, leave(item)? }
dropTarget: {
acceptDrop(image) {
// Do something with image! for example,
DocumentActionCreators.setImage(this.props.blockId, image);
}
}
});
},
render() {
// {...this.dropTargetFor(ItemTypes.IMAGE)} will expand into
// { onDragEnter: (handled by mixin), onDragOver: (handled by mixin), onDragLeave: (handled by mixin), onDrop: (handled by mixin) }.
return (
<div {...this.dropTargetFor(ItemTypes.IMAGE)}>
{this.props.image &&
<img src={this.props.image.url} />
}
</div>
);
}
);
Kéo nguồn + thả mục tiêu trong một thành phần
Giả sử bây giờ chúng tôi muốn người dùng có thể kéo hình ảnh ra khỏi ImageBlock
. Chúng tôi chỉ cần thêm thích hợp dragSource
vào nó và một vài trình xử lý:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var ImageBlock = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
registerType(ItemTypes.IMAGE, {
// Add a drag source that only works when ImageBlock has an image:
dragSource: {
canDrag() {
return !!this.props.image;
},
beginDrag() {
return {
item: this.props.image
};
}
}
dropTarget: {
acceptDrop(image) {
DocumentActionCreators.setImage(this.props.blockId, image);
}
}
});
},
render() {
return (
<div {...this.dropTargetFor(ItemTypes.IMAGE)}>
{/* Add {...this.dragSourceFor} handlers to a nested node */}
{this.props.image &&
<img src={this.props.image.url}
{...this.dragSourceFor(ItemTypes.IMAGE)} />
}
</div>
);
}
);
Điều gì khác có thể xảy ra?
Tôi chưa đề cập đến mọi thứ nhưng có thể sử dụng API này theo một số cách khác:
- Sử dụng
getDragState(type)
vàgetDropState(type)
để tìm hiểu xem tính năng kéo có hoạt động hay không và sử dụng nó để chuyển đổi các lớp hoặc thuộc tính CSS;
- Chỉ định
dragPreview
để Image
sử dụng hình ảnh làm trình giữ chỗ kéo (sử dụng ImagePreloaderMixin
để tải chúng);
- Giả sử, chúng tôi muốn đặt
ImageBlocks
hàng lại. Chúng tôi chỉ cần họ thực hiện dropTarget
và dragSource
cho ItemTypes.BLOCK
.
- Giả sử chúng ta thêm các loại khối khác. Chúng ta có thể sử dụng lại logic sắp xếp lại của chúng bằng cách đặt nó vào một mixin.
dropTargetFor(...types)
cho phép chỉ định nhiều loại cùng một lúc, vì vậy một vùng thả có thể bắt nhiều loại khác nhau.
- Khi bạn cần kiểm soát chi tiết hơn, hầu hết các phương thức được truyền sự kiện kéo gây ra chúng như là tham số cuối cùng.
Để có tài liệu cập nhật và hướng dẫn cài đặt, hãy truy cập repo react-dnd trên Github .