react 拖拽排序组件git 仓库源码
基于 html5 的 dragable 属性,可以自己封装个拖拽组件,react 相关的拖拽组件都非常的难以使用。只要掌握拖拽的原理,那么封装个这样的组件也不是难事,我最开始的难点就是,只能知道拖拽后在哪一个 element 上,但是并不知道放在哪个位置上去。最后观察拖拽组件,发现这个放置的位置是跟现在拖拽的元素和最后停留的元素位置上有关系的。
安装
npm install --save mys-react
使用
import React from "react";
import { Drag } from "mys-react";
export default class Test extends React.Component {
state = {
list: [1, 2, 3, 4]
};
render() {
return (
<div>
<Drag
style={{ width: 100, margin: 100 }}
list={list}
onChange={list => {
console.log(list); // 返回排序结果
this.setState({ list });
}}
>
{list.map(v => {
return (
<div style={{ borderBottom: "1px solid #ddd" }} key={v}>
{v}
</div>
);
})}
</Drag>
</div>
);
}
}
原理解析
- 1、获取到组件的子元素,vue 中就是插槽内容
import React from "react";
export default class Drag extends React.Component {
componentDidMount() {
this.init();
}
componentWillReceiveProps(nextProps) {
if (nextProps.list !== this.props.list) {
this.init(nextProps);
}
}
init(nextProps) {
const { children, list } = nextProps || this.props;
this.setState({ children, list }, () => {
this.initItem(this.dragContainer);
});
}
}
- 2、给子元素每项设置属性
initItem(dragContainer) {
const items = Array.prototype.slice.call(dragContainer.children);
const that = this;
items.forEach((v, k) => {
that.addAttributes(v);
v.ondragstart = function(e) {
that.dragIndex = k;
e.dataTransfer.setData("Text", k);
};
v.ondragover = function() {
that.currentIndex = k;
};
v.ondragend = function() {
const { currentIndex, dragIndex } = that;
let { children } = that.state;
if (currentIndex === dragIndex) return;
children = that.getNewList(children);
that.setState({ children }, () => {
that.updateList();
});
};
});
}
addAttributes(v) {
const { cursor } = this.props;
v.draggable = true;
v.style.cursor = cursor || "move";
v.style.marginBottom = "10px";
}
- 3、拖拽排在位置的确定,这也是核心部分,就是现在拖拽的元素 key 小于停留的元素,那么放置其后,如果现在拖拽的元素 key 大于于停留的元素,放置其前
getNewList(list) {
const { currentIndex, dragIndex } = this;
if (currentIndex === dragIndex) return;
if (currentIndex < dragIndex) {
//放在前面
list.splice(currentIndex, 0, list[dragIndex]);
list.splice(dragIndex + 1, 1);
}
if (currentIndex > dragIndex) {
//放在后面
list.splice(currentIndex + 1, 0, list[dragIndex]);
list.splice(dragIndex, 1);
}
return list;
}
注意事项
这个拖拽组件可以与 antd 的组件嵌套使用,但是组件的子元素第一层必须是 html5 的标签内容。比如
- 错误的示范
<Drag
style={{ width: 100, margin: 100 }}
list={list}
onChange={list => {
console.log(list); // 返回排序结果
this.setState({ list });
}}
>
{list.map(v => {
return (
// 直接使用antd的组件
<Button style={{ borderBottom: "1px solid #ddd" }} key={v}>
{v}
</Button>
);
})}
</Drag>
- 正确的用法
<Drag
style={{ width: 100, margin: 100 }}
list={list}
onChange={list => {
console.log(list); // 返回排序结果
this.setState({ list });
}}
>
{list.map(v => {
return (
// 先使用html5标签,再使用antd的组件
<div>
<Button style={{ borderBottom: "1px solid #ddd" }} key={v}>
{v}
</Button>
</div>
);
})}
</Drag>