使用指南
安装
npm install he-tree-reactpnpm add he-tree-reactyarn add he-tree-react数据类型
此库支持两种结构的数据:
- 扁平数据, 即一个一维数组. 类似与存储在数据库中的数据. 每项需要
id, 父级 id,null代表没有父级. 扁平数据的顺序必须跟树一样, 你可以在初始化数据时使用sortFlatData方法给数据排序.js[ { id: 1, pid: null }, { id: 2, pid: 1 }, { id: 3, pid: 2 }, ]; - 树形数据. 使用
children数组包含子节点. 如果未指定id, 此库将使用节点在树中的索引作为id. 使用树形数据时需设置dataType: 'tree'.
[
{
id: 1,
children: [
{
id: 2,
children: [{ id: 3 }],
},
],
},
];数据中的id, pid, children不是固定的. 在设置中, 使用idKey, parentIdKey, childrenKey表明你的数据中的对应键名.
没有组件
此库没有导出组件,而是导出一个 hook useHeTree. 使用它返回的renderTree渲染树. 这样做的好处是除了renderTree, useHeTree还会返回一些内部状态和方法, 可以轻松的被获取.
import { useHeTree } from "he-tree-react";
export default function App() {
const { renderTree } = useHeTree({...})
return <div>
{renderTree()}
</div>
}选项
useHeTree是主要使用的函数, 它的第一个参数是选项对象. 必须的选项有data, 必须两者中有一个的是renderNode, renderNodeBox. 其他重要选项是:
dataType, 表明数据类型. 可用值:flat, 默认. 扁平数据.tree, 树形数据.
idKey, parentIdKey, 默认值是id和parent_id. 使用扁平数据时需要. 虽然有默认值, 但还是建议写明更好.childrenKey, 默认是children. 使用树形数据时需要. 虽然有默认值, 但还是建议写明更好.onChange, 数据改变时调用的函数, 参数是新数据. 如果你的树不会改变则不需要.isFunctionReactive, 布尔. 默认false.useHeTree选项中包含许多回调函数, 如onChange, canDrop.isFunctionReactive可用来控制是否监听这些回调函数的改变. 如果你的回调函数和data是同步改变的, 则不用启用此项. 否则你需要启用此项, 并且用 React 的useCallback或useMemo缓存你的所有回调函数以避免性能问题.
提示
stat, 单个节点的相关信息. 大部分回调函数的参数里有stat. 参考StatAPI.node, 节点的数据. 通过stat.node可以获取节点数据.getStat, 通过此函数可以获取stat, 唯一参数可以是id, node, stat. 此函数在useHeTree的返回对象中:const {getStat} = useHeTree({...}).- 下面的代码例子附带有运行效果. 这些例子可以直接复制使用. 注意其中的高亮行的代码.
- 下面的代码例子使用
tsx格式, 如果你需要js格式, 可以使用任意 ts js 在线转换器.
基础使用-扁平数据
import { useHeTree, sortFlatData } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
const [data, setdata] = useState(() => sortFlatData([
{
id: 1,
parent_id: null,
name: "Root Category",
},
{
id: 2,
parent_id: 1,
name: "Technology",
},
{
id: 5,
parent_id: 2,
name: "Hardware",
},
{
id: 10,
parent_id: 5,
name: "Computer Components",
},
{
id: 4,
parent_id: 2,
name: "Programming",
},
{
id: 8,
parent_id: 4,
name: "Python",
},
{
id: 3,
parent_id: 1,
name: "Science",
},
{
id: 7,
parent_id: 3,
name: "Biology",
},
{
id: 6,
parent_id: 3,
name: "Physics",
},
], keys));
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, open, checked }) => <div>
{node.name}
</div>,
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}基础使用-树形数据
import { useHeTree } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const [data, setdata] = useState(() => [
{
id: 1,
name: "Root Category",
children: [
{
id: 2,
name: "Technology",
children: [
{
id: 5,
name: "Hardware",
children: [
{
id: 10,
name: "Computer Components",
children: [],
},
],
},
{
id: 4,
name: "Programming",
children: [
{
id: 8,
name: "Python",
children: [],
},
],
},
],
},
{
id: 3,
name: "Science",
children: [
{
id: 7,
name: "Biology",
children: [],
},
{
id: 6,
name: "Physics",
children: [],
},
],
},
],
},
]);
const { renderTree } = useHeTree({
data,
dataType: 'tree',
childrenKey: 'children',
onChange: setdata,
renderNode: ({ id, node, open, checked }) => <div>
{node.name}
</div>,
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}自定义拖拽触发元素
给节点任意子元素添加draggable属性即可.
import { useHeTree, sortFlatData } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<button draggable={draggable}>Drag</button>
{node.name}
</div>,
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}节点 HTML 结构和样式
节点 HTML 如下:
<div
draggable="true"
data-key="1"
data-level="1"
data-node-box="true"
style="padding-left: 0px;"
>
<div>Node</div>
</div>上面有两个 div. 使用renderNode选项控制内层 div 的渲染. 如: renderNode: ({node}) => <div>{node.name}</div>.
外层节点被称为nodeBox, 不要修改它的padding-left, padding-right. 使用选项indent控制节点的缩进. 如果你想控制nodeBox或拖拽占位节点的渲染, 可以使用renderNodeBox选项, 这将覆盖renderNode. 标准的renderNodeBox如下:
renderNodeBox: ({ stat, attrs, isPlaceholder }) => (
<div {...attrs} key={attrs.key}>
{isPlaceholder ? (
<div
className="he-tree-drag-placeholder"
style={{ minHeight: "20px", border: "1px dashed blue" }}
/>
) : (
<div>{/* node area */}</div>
)}
</div>
);第 4 到第 7 行是拖拽占位节点. 第 9 行是节点元素.
自定义拖拽占位节点和 node box
import { useHeTree, sortFlatData } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNodeBox: ({ stat, attrs, isPlaceholder }) => (
<div {...attrs} key={attrs.key}>
{isPlaceholder ? <div className="my-drag-placeholder">drop here</div>
: <div className="mynode">{stat.node.name}</div>
}
</div>
),
})
return <div>
{renderTree({ className: 'mytree', style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
<style>{`
.mytree [data-node-box]{
padding: 5px 0;
}
.mytree [data-node-box]:hover{
background-color: #eee;
}
.mytree .he-tree-drag-placeholder{
height: 30px;
line-height: 30px;
text-align: center;
border: 1px dashed red;
}
.mynode{
padding-left:5px;
}
`}</style>
</div>
}节点的展开与折叠
- 使用选项
openIds表明展开的节点. - 可通过
stat.open获取该节点的open状态. useHeTree返回的allIds包含所有节点的 id.- 此库导出了方法可以展开单个或多个节点的所有父级. 扁平数据:
openParentsInFlatData. 树形数据:openParentsInTreeData.
import { useHeTree, sortFlatData, openParentsInFlatData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const [openIds, setopenIds] = useState<Id[] | undefined>([]);
const handleOpen = (id: Id, open: boolean) => {
if (open) {
setopenIds([...(openIds || allIds), id]);
} else {
setopenIds((openIds || allIds).filter((i) => i !== id));
}
}
const { renderTree, allIds } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
openIds,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<button onClick={() => handleOpen(id, !open)}>{open ? '-' : '+'}</button>
{node.name} - {id}
</div>,
})
return <div>
<button onClick={() => setopenIds(allIds)}>Open All</button>
<button onClick={() => setopenIds([])}>Close All</button>
<button onClick={() => setopenIds(openParentsInFlatData(data, openIds || allIds, 8, keys))}>Open 'Python'</button>
<button onClick={() => setopenIds(openParentsInFlatData(data, [], 8, keys))}>Only Open 'Python'</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}此例子顶部 4 个按钮分别是: 展开全部, 折叠全部, 展开'Python'节点的所有父节点, 仅展开'Python'节点的所有父节点.
节点的勾选
- 使用选项
checkedIds表明勾选的节点. - 可通过
stat.checked获取该节点的checked状态. - 此库导出了方法可以获取单个或多个节点
checked变动后的checkedIds. 扁平数据:updateCheckedInFlatData. 树形数据: `updateCheckedInTreeData.- 此方法对节点的
checked的更新是级联的. 如果你不想级联更新, 使用你自己的逻辑替代. - 此方法返回一个长度 2 的数组. 第一项是所有勾选的 id, 第二项是所有半选的 id. 如果不需要半选, 忽略第二项.
- 半选, 即同时有子节点被勾选或半选, 也有子节点未被勾选.
- 此方法对节点的
import { useHeTree, sortFlatData, updateCheckedInFlatData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const [checkedIds, setcheckedIds] = useState<Id[]>([]);
const [semiCheckedIds, setsemiCheckedIds] = useState<Id[]>([]);
const handleChecked = (id: Id, checked: boolean) => {
const r = updateCheckedInFlatData(data, checkedIds, id, checked, keys);
setcheckedIds(r[0]);
setsemiCheckedIds(r[1]);
}
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
checkedIds,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<input type="checkbox" checked={checked || false} onChange={() => handleChecked(id, !checked)} />
{node.name} - {id}
</div>,
})
return <div>
Checked: {JSON.stringify(checkedIds)} <br />
Semi-Checked: {JSON.stringify(semiCheckedIds)}
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}控制是否可拖拽, 可放入
使用以下选项控制:
canDrag, 节点是否可拖拽.canDrop, 节点是否可放入.canDropToRoot, 树根是否可放入.
import { useHeTree, sortFlatData } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, open, checked, draggable }) => <div>
{node.name} - {id}
</div>,
canDrag: ({ id }) => id === 2 ? true : (id === 3 ? false : undefined),
canDrop: ({ id }) => id === 3 ? true : (id === 2 ? false : undefined),
canDropToRoot: (index) => false,
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}- 根节点不可放入.
Technology及子节点可以拖拽.Science及子节点不可以拖拽.Science及子节点可以放入.Technology及子节点不可以放入.
拖拽到节点上时打开节点
使用以下选项控制:
dragOpen, 是否启用, 默认false.dragOpenDelay, 延时, 默认600毫秒.onDragOpen, 打开节点时调用的函数.
import { useHeTree, sortFlatData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const [openIds, setopenIds] = useState<Id[] | undefined>([1, 3]);
const handleOpen = (id: Id, open: boolean) => {
if (open) {
setopenIds([...(openIds || allIds), id]);
} else {
setopenIds((openIds || allIds).filter((i) => i !== id));
}
}
const { renderTree, allIds } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
openIds,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<button onClick={() => handleOpen(id, !open)}>{open ? '-' : '+'}</button>
{node.name} - {id}
</div>,
dragOpen: true,
onDragOpen(stat) {
handleOpen(stat.id, true)
},
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}更新数据
由于 React 的不可变特性, 扁平数据和树形数据更新都很困难. 针对扁平数据, 此库提供了两个方法, 用以增加节点或删除节点. 如果你要进行更复杂的操作, 或者更新树形数据, 推荐你使用immer.
npm install immer use-immerpnpm add immer use-immeryarn add immer use-immer使用内置方法更新扁平数据
addToFlatData: 增加节点. removeByIdInFlatData: 删除节点. 这两个方法都会改变原数据, 所以把原数据的复制传给它, 或者与immer一起使用.
import {
useHeTree, sortFlatData,
addToFlatData, removeByIdInFlatData
} from "he-tree-react";
import type { Id } from "he-tree-react";
import { useRef, useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const add = (pid: Id) => {
let id = parseInt(Math.random().toString().substring(2, 5));
let newData = [...data];
addToFlatData(newData, { id, parent_id: pid as number, name: "New" }, 0, keys)
setdata(newData);
}
const remove = (id: Id) => {
let newData = [...data];
removeByIdInFlatData(newData, id as number, keys)
setdata(newData);
}
const initialData = useRef<typeof data>();
initialData.current = initialData.current || data;
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, draggable }) => <div>
<button draggable={draggable}>👉</button>
{node.name} - {id} -
<button onClick={() => add(id)}>+</button>
<button onClick={() => remove(id)}>-</button>
</div>,
})
return <div>
<button onClick={() => setdata(initialData.current!)}>Restore</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}使用 immer 更新扁平数据
注意, 这里使用了useImmer替代 React 的useState.
import {
useHeTree, sortFlatData,
addToFlatData, removeByIdInFlatData
} from "he-tree-react";
import type { Id } from "he-tree-react";
import { useRef } from 'react';
import { useImmer } from "use-immer";
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useImmer(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const add = (pid: Id) => {
let id = parseInt(Math.random().toString().substring(2, 5));
setdata(draft => {
addToFlatData(draft, { id, parent_id: pid as number, name: "New" }, 0, keys)
});
}
const remove = (id: Id) => {
setdata(draft => {
removeByIdInFlatData(draft, id as number, keys)
})
}
const edit = (id: Id) => {
let newName = prompt("Enter new name")
setdata(draft => {
if (newName) {
draft.find(node => node.id === id)!.name = newName
}
})
}
const initialData = useRef<typeof data>();
initialData.current = initialData.current || data;
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, draggable }) => <div>
<button draggable={draggable}>👉</button>
{node.name} - {id} -
<button onClick={() => add(id)}>+</button>
<button onClick={() => remove(id)}>-</button>
<button onClick={() => edit(id)}>Edit</button>
</div>,
})
return <div>
<button onClick={() => setdata(initialData.current!)}>Restore</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}使用 immer 更新树形数据
注意, 这里使用了useImmer替代 React 的useState. findTreeData方法类似数组的find方法.
import { useHeTree, findTreeData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useRef } from 'react';
import { useImmer } from "use-immer";
export default function BasePage() {
const CHILDREN = 'children'
const keys = { idKey: 'id', childrenKey: CHILDREN };
// prettier-ignore
const [data, setdata] = useImmer(() => [{ id: 1, name: "Root Category", children: [{ id: 2, name: "Technology", children: [{ id: 5, name: "Hardware", children: [{ id: 10, name: "Computer Components", children: [], },], }, { id: 4, name: "Programming", children: [{ id: 8, name: "Python", children: [], },], },], }, { id: 3, name: "Science", children: [{ id: 7, name: "Biology", children: [], }, { id: 6, name: "Physics", children: [], },], },], },]);
const add = (pid: Id) => {
let id = parseInt(Math.random().toString().substring(2, 5));
setdata(draft => {
findTreeData(draft, (node) => node.id === pid, CHILDREN)![CHILDREN].unshift({ id, name: "New", [CHILDREN]: [], })
})
}
const remove = (id: Id, pid: Id | null) => {
setdata(draft => {
const children = findTreeData(draft, (node,) => node.id === pid, CHILDREN)![CHILDREN]
children.splice(children.findIndex(t => t.id === id), 1)
})
}
const edit = (id: Id) => {
let newName = prompt("Enter new name")
setdata(draft => {
if (newName) {
findTreeData(draft, (node) => node.id === id, CHILDREN)!.name = newName
}
})
}
const initialData = useRef<typeof data>();
initialData.current = initialData.current || data;
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'tree',
onChange: setdata,
renderNode: ({ id, pid, node, draggable }) => <div>
<button draggable={draggable}>👉</button>
{node.name} - {id} -
<button onClick={() => add(id)}>+</button>
<button onClick={() => remove(id, pid)}>-</button>
<button onClick={() => edit(id)}>Edit</button>
</div>,
})
return <div>
<button onClick={() => setdata(initialData.current!)}>Restore</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}从外部发起的拖拽
相关选项:
onExternalDragOver: 表明是否处理外部拖拽.onExternalDrop: 当外部拖拽放入树中时调用的回调函数.
import { useHeTree, sortFlatData, addToFlatData } from "he-tree-react";
import { useImmer } from "use-immer";
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useImmer(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const { renderTree, allIds } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, open, checked, draggable }) => <div>
{node.name} - {id}
</div>,
onExternalDragOver: (e) => true,
onExternalDrop: (e, parentStat, index) => {
setdata(draft => {
const newNode = { id: 100 + data.length, parent_id: parentStat?.id ?? null, name: "New Node" }
addToFlatData(draft, newNode, index, keys)
})
},
})
return <div>
<button draggable={true}>Drag me in to the tree</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}超大数据
使用选项virtual启用虚拟列表功能. 记得给树设置可见区域高度.
import { useHeTree, sortFlatData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'pid' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData(createData(), keys));
const [openIds, setopenIds] = useState<Id[] | undefined>([]);
const handleOpen = (id: Id, open: boolean) => {
if (open) {
setopenIds([...(openIds || allIds), id]);
} else {
setopenIds((openIds || allIds).filter((i) => i !== id));
}
}
const { renderTree, allIds } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
openIds,
virtual: true,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<button onClick={() => handleOpen(id, !open)}>{open ? '-' : '+'}</button>
{id}
</div>,
})
return <div>
{renderTree({ style: { width: '300px', height: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
// generate 10000 nodes
function createData() {
const genId = () => result.length
const result: { id: number, pid: number | null }[] = [];
for (let i = 0; i < 1000; i++) {
let id1 = genId()
result.push({ id: id1, pid: null })
for (let j = 0; j < 4; j++) {
result.push({ id: genId(), pid: id1 })
}
let id2 = genId()
result.push({ id: id2, pid: null })
for (let j = 0; j < 4; j++) {
result.push({ id: genId(), pid: id2 })
}
}
return result;
}触摸 & 移动设备
此库基于 HTML5 Drag and Drop API, 所以在支持 Drag and Drop API 的移动设备上能工作. 如果不支持, 可以尝试添加兼容 Drag and Drop API 的库.
注意
触摸时, 用户需要触摸并等一会儿才能触发拖拽。
其他
- 选项
direction: 从右往左显示. - 选项
customDragImage: 自定义 drag image. - 选项
rootId: 使用扁平数据时, 顶级节点的父 id. - 选项
keepPlaceholder: 拖拽到树外时, 是否要保留拖拽占位节点. 默认false. - 辅助方法
scrollToNode: 滚动到指定节点.