Skip to content

Commit 2bbca0e

Browse files
committed
docs: zh guide
1 parent 2fa9b14 commit 2bbca0e

17 files changed

+710
-6
lines changed

docs/zh/v1/guide.md

Lines changed: 195 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ yarn add he-tree-react
2222

2323
此库支持两种结构的数据:
2424

25-
- 扁平数据, 即一个一维数组. 类似与存储在数据库中的数据. 每项需要`id`, 父级 id, `null`代表没有父级. 扁平数据的顺序必须准确, 你需要在初始化数据时使用[`sortFlatData`](./api#sortflatdata)方法给数据排序.
25+
- 扁平数据, 即一个一维数组. 类似与存储在数据库中的数据. 每项需要`id`, 父级 id, `null`代表没有父级. 扁平数据的顺序必须跟树一样, 你可以在初始化数据时使用[`sortFlatData`](./api#sortflatdata)方法给数据排序.
2626
```js
2727
[
2828
{ id: 1, pid: null },
@@ -51,7 +51,7 @@ yarn add he-tree-react
5151
## 没有组件
5252

5353
此库没有导出组件,而是导出一个 hook `useHeTree`. 使用它返回的`renderHeTree`渲染树. 这样做的好处是除了`renderHeTree`,
54-
`useHeTree`还会返回一些内部状态和方法, 可以轻松的被使用.
54+
`useHeTree`还会返回一些内部状态和方法, 可以轻松的被获取.
5555

5656
```js
5757
import { useHeTree } from "he-tree-react";
@@ -64,7 +64,29 @@ export default function App() {
6464
}
6565
```
6666

67-
## 基础使用-平面数据
67+
## 选项
68+
69+
`useHeTree`是主要使用的函数, 它的第一个参数是选项对象. 必须的选项有`data`, 必须两者中有一个的是`renderNode, renderNodeBox`. 其他重要选项是:
70+
71+
- `dataType`, 表明数据类型. 可用值:
72+
- `flat`, 默认. 扁平数据.
73+
- `tree`, 树形数据.
74+
- `idKey, parentIdKey`, 默认值是`id``parent_id`. 使用扁平数据时需要. 虽然有默认值, 但还是建议写明更好.
75+
- `childrenKey`, 默认是`children`. 使用树形数据时需要. 虽然有默认值, 但还是建议写明更好.
76+
- `onChange`, 数据改变时调用的函数, 参数是新数据. 如果你的树不会改变则不需要.
77+
- `isFunctionReactive`, 布尔. 默认`false`. `useHeTree`选项中包含许多回调函数, 如`onChange, canDrop`. `isFunctionReactive`可用来控制是否监听这些回调函数的改变. 如果你的回调函数和`data`是同步改变的, 则不用启用此项. 否则你需要启用此项, 并且用 React 的`useCallback``useMemo`缓存你的所有回调函数以避免性能问题.
78+
79+
[查看`useHeTree`的 API 文档以了解更多](api#usehetree).
80+
81+
## 提示
82+
83+
- `stat`, 单个节点的相关信息. 大部分回调函数的参数里有`stat`. [参考`Stat` API](api#stat).
84+
- `node`, 节点的数据. 通过`stat.node`可以获取节点数据.
85+
- `getStat`, 通过此函数可以获取`stat`, 唯一参数可以是`id, node, stat`. 此函数在`useHeTree`的返回对象中: `const {getStat} = useHeTree({...})`.
86+
- 下面的代码例子附带有运行效果. 这些例子可以直接复制使用. 注意其中的高亮行的代码.
87+
- 下面的代码例子使用`tsx`格式, 如果你需要`js`格式, 可以使用任意 ts js 在线转换器.
88+
89+
## 基础使用-扁平数据
6890

6991
<<< @/../src/pages/base_flat_data.tsx
7092
<DemoIframe url="/base_flat_data" />
@@ -73,3 +95,173 @@ export default function App() {
7395

7496
<<< @/../src/pages/base_tree_data.tsx
7597
<DemoIframe url="/base_tree_data" />
98+
99+
## 自定义拖拽触发元素
100+
101+
给节点任意子元素添加`draggable`属性即可.
102+
103+
<<< @/../src/pages/custom_drag_trigger_flat_data.tsx{14}
104+
<DemoIframe url="/custom_drag_trigger_flat_data" />
105+
106+
## 节点 HTML 结构和样式
107+
108+
节点 HTML 如下:
109+
110+
```html
111+
<div
112+
draggable="true"
113+
data-key="1"
114+
data-level="1"
115+
data-node-box="true"
116+
style="padding-left: 0px;"
117+
>
118+
<div>Node</div>
119+
</div>
120+
```
121+
122+
上面有两个 div. 使用`renderNode`选项控制内层 div 的渲染. 如: `renderNode: ({node}) => <div>{node.name}</div>`.
123+
124+
外层节点被称为`nodeBox`, 不要修改它的`margin, padding-left, padding-right`, 不要给它的父元素设置`gap`. 如果你想控制`nodeBox`或拖拽占位节点的渲染, 可以使用`renderNodeBox`选项, 这将覆盖`renderNode`. 标准的`renderNodeBox`如下:
125+
126+
```tsx{4-7,9}
127+
renderNodeBox: ({ stat, attrs, isPlaceholder }) => (
128+
<div {...attrs}>
129+
{isPlaceholder ? (
130+
<div
131+
className="he-tree-drag-placeholder"
132+
style={{ minHeight: "20px", border: "1px dashed blue" }}
133+
/>
134+
) : (
135+
<div>{/* node area */}</div>
136+
)}
137+
</div>
138+
);
139+
```
140+
141+
第 4 到第 7 行是拖拽占位节点. 第 9 行是节点元素.
142+
143+
## 自定义拖拽占位节点和 node box
144+
145+
<<< @/../src/pages/customize_placeholder_and_node_box.tsx{13-19,23-39}
146+
<DemoIframe url="/customize_placeholder_and_node_box" />
147+
148+
## 节点的展开与折叠
149+
150+
- 使用选项`openIds`表明展开的节点.
151+
- 可通过`stat.open`获取该节点的`open`状态.
152+
- `useHeTree`返回的`allIds`包含所有节点的 id.
153+
- 此库导出了方法可以展开单个或多个节点的所有父级. 扁平数据: [`openParentsInFlatData`](api#openparentsinflatdata). 树形数据: [`openParentsInTreeData`](api#openparentsintreedata).
154+
155+
<<< @/../src/pages/open_ids.tsx{1,9-16,22-24,29-32}
156+
<DemoIframe url="/open_ids" />
157+
此例子顶部 4 个按钮分别是: 展开全部, 折叠全部, 展开'Python'节点的所有父节点, 仅展开'Python'节点的所有父节点.
158+
159+
## 节点的勾选
160+
161+
- 使用选项`checkedIds`表明勾选的节点.
162+
- 可通过`stat.checked`获取该节点的`checked`状态.
163+
- 此库导出了方法可以获取单个或多个节点`checked`变动后的`checkedIds`. 扁平数据: [`updateCheckedInFlatData`](api#updatecheckedinflatdata). 树形数据: [`updateCheckedInTreeData](api#updatecheckedintreedata).
164+
- 此方法对节点的`checked`的更新是级联的. 如果你不想级联更新, 使用你自己的逻辑替代.
165+
- 此方法返回一个长度 2 的数组. 第一项是所有勾选的 id, 第二项是所有半选的 id. 如果不需要半选, 忽略第二项.
166+
- 半选, 即同时有子节点被勾选或半选, 也有子节点未被勾选.
167+
168+
<<< @/../src/pages/checked_ids.tsx{1,9-15,21-23,28-29}
169+
<DemoIframe url="/checked_ids" />
170+
171+
## 控制是否可拖拽, 可放入
172+
173+
使用以下选项控制:
174+
175+
- [`canDrag`](api#candrag), 节点是否可拖拽.
176+
- [`canDrop`](api#candrop), 节点是否可放入.
177+
- [`canDropToRoot`](api#candroptoroot), 树根是否可放入.
178+
179+
<<< @/../src/pages/draggable_droppable.tsx{16-18}
180+
<DemoIframe url="/draggable_droppable" />
181+
182+
- 根节点不可放入.
183+
- `Technology`及子节点可以拖拽. `Science`及子节点不可以拖拽.
184+
- `Science`及子节点可以放入. `Technology`及子节点不可以放入.
185+
186+
## 拖拽到节点上时打开节点
187+
188+
使用以下选项控制:
189+
190+
- [`dragOpen`](api#dragopen), 是否启用, 默认`false`.
191+
- [`dragOpenDelay`](api#dragopen), 延时, 默认 `600` 毫秒.
192+
- [`onDragOpen`](api#ondragopen), 打开节点时调用的函数.
193+
194+
<<< @/../src/pages/dragopen.tsx
195+
<DemoIframe url="/dragopen" />
196+
197+
## 更新数据
198+
199+
由于 React 的不可变特性, 扁平数据和树形数据更新都很困难. 针对扁平数据, 此库提供了两个方法, 用以增加节点或删除节点. 如果你要进行更复杂的操作, 或者更新树形数据, 推荐你使用[`immer`](https://github.com/mweststrate/immer).
200+
::: code-group
201+
202+
```sh [npm]
203+
npm install immer use-immer
204+
```
205+
206+
```sh [pnpm]
207+
pnpm add immer use-immer
208+
```
209+
210+
```sh [yarn]
211+
yarn add immer use-immer
212+
```
213+
214+
:::
215+
216+
## 使用内置方法更新扁平数据
217+
218+
[`addToFlatData`](api#addtoflatdata): 增加节点. [`removeByIdInFlatData`](api#removebyidinflatdata): 删除节点.
219+
这两个方法都会改变原数据, 所以把原数据的复制传给它, 或者与`immer`一起使用.
220+
221+
<<< @/../src/pages/update_data.tsx{3,12-22,33-34}
222+
<DemoIframe url="/update_data" />
223+
224+
## 使用 immer 更新扁平数据
225+
226+
注意, 这里使用了`useImmer`替代 React 的`useState`.
227+
228+
<<< @/../src/pages/update_flat_data_with_immer.tsx{3,7,12,13-31,42-44}
229+
<DemoIframe url="/update_flat_data_with_immer" />
230+
231+
## 使用 immer 更新树形数据
232+
233+
注意, 这里使用了`useImmer`替代 React 的`useState`. `findTreeData`方法类似数组的`find`方法.
234+
235+
<<< @/../src/pages/update_tree_data_with_immer.tsx{1,4,10-30,41-43}
236+
<DemoIframe url="/update_tree_data_with_immer" />
237+
238+
## 从外部发起的拖拽
239+
240+
相关选项:
241+
242+
- [`onExternalDragOver`](api#onexternaldragover): 表明是否处理外部拖拽.
243+
- [`onExternalDrop`](api#onexternaldrop): 当外部拖拽放入树中时调用的回调函数.
244+
245+
<<< @/../src/pages/external_drag.tsx{16-22,25}
246+
<DemoIframe url="/external_drag" />
247+
248+
## 超大数据
249+
250+
使用选项[`virtual`](api#virtual)启用虚拟列表功能. 记得给树设置可见区域高度.
251+
252+
<<< @/../src/pages/virtual_list.tsx{23,30}
253+
<DemoIframe url="/virtual_list" />
254+
255+
## 触摸 & 移动设备
256+
257+
此库基于 HTML5 Drag and Drop API, 所以在支持 Drag and Drop API 的移动设备上能工作. 如果不支持, 可以尝试添加兼容 Drag and Drop API 的库.
258+
259+
::: tip 注意
260+
触摸时, 用户需要触摸并等一会儿才能触发拖拽。
261+
:::
262+
263+
## 其他
264+
265+
- 选项 [`direction`](api#direction): 从右往左显示.
266+
- 选项 [`customDragImage`](api#customdragimage): 自定义 drag image.
267+
- 选项 [`rootId`](api#rootid): 使用扁平数据时, 顶级节点的父 id.

lib/HeTree.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export const defaultProps = {
5050
dataType: 'flat' as 'tree' | 'flat',
5151
direction: 'ltr' as 'ltr' | 'rtl',
5252
rootId: null as Id | null,
53+
virtual: false,
5354
}
5455

5556
export interface HeTreeProps<T extends Record<string, any>> extends Partial<typeof defaultProps> {
@@ -648,7 +649,7 @@ export function useHeTree<T extends Record<string, any>>(
648649
}
649650
return (
650651
<div className={`he-tree ${options?.className || ''}`} style={options?.style} ref={rootRef} onDragOver={onDragOverRoot} onDrop={onDropToRoot} onDragEnd={onDragEndOnRoot}>
651-
<VirtualList<Id> ref={virtualListRef} items={visibleIds} virtual={false} persistentIndices={persistentIndices}
652+
<VirtualList<Id> ref={virtualListRef} items={visibleIds} virtual={props.virtual} persistentIndices={persistentIndices} style={{ height: '100%' }}
652653
renderItem={(id, index) => renderNodeBox({
653654
stat: getStat(id)!, attrs: attrsList[index], isPlaceholder: id === placeholderId
654655
})}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"eslint": "^8.55.0",
5858
"eslint-plugin-react-hooks": "^4.6.0",
5959
"eslint-plugin-react-refresh": "^0.4.5",
60+
"immer": "^10.0.3",
6061
"jsdom": "^24.0.0",
6162
"react-router-dom": "^6.22.2",
6263
"react-test-renderer": "^18.2.0",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/PageLayout.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,50 @@ export default function PageLayout(props: {}) {
2121
title: 'Base - Tree Data',
2222
path: '/base_tree_data',
2323
},
24+
{
25+
title: 'Custom Drag Trigger',
26+
path: '/custom_drag_trigger_flat_data',
27+
},
28+
{
29+
title: 'Open',
30+
path: '/open_ids',
31+
},
32+
{
33+
title: 'Checked',
34+
path: '/checked_ids',
35+
},
36+
{
37+
title: 'Update Data',
38+
path: '/update_data',
39+
},
40+
{
41+
title: 'Update Flat Data With Immer',
42+
path: '/update_flat_data_with_immer',
43+
},
44+
{
45+
title: 'Update Tree Data With Immer',
46+
path: '/update_tree_data_with_immer',
47+
},
48+
{
49+
title: 'Customize Placeholder and Node Box',
50+
path: '/customize_placeholder_and_node_box',
51+
},
52+
{
53+
title: 'Draggable & Droppable',
54+
path: '/draggable_droppable'
55+
},
56+
{
57+
title: 'Open Node when Drag over',
58+
path: '/dragopen'
59+
},
60+
{
61+
title: 'Drag and Drop to External',
62+
path: '/external_drag'
63+
},
64+
{
65+
title: 'Big Data with Virtual List',
66+
path: '/virtual_list'
67+
},
2468
{
2569
title: 'Home',
2670
path: '/',

src/main.tsx

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,18 @@ import PageLayout from './PageLayout.tsx';
1111
const Pages = {
1212
home: lazy(() => import("./pages/index.tsx")),
1313
base_tree_data: lazy(() => import("./pages/base_tree_data.tsx")),
14-
base_flat_data: lazy(() => import("./pages/base_flat_data.tsx"))
14+
base_flat_data: lazy(() => import("./pages/base_flat_data.tsx")),
15+
custom_drag_trigger_flat_data: lazy(() => import("./pages/custom_drag_trigger_flat_data.tsx")),
16+
open_ids: lazy(() => import("./pages/open_ids.tsx")),
17+
checked_ids: lazy(() => import("./pages/checked_ids.tsx")),
18+
update_data: lazy(() => import("./pages/update_data.tsx")),
19+
update_flat_data_with_immer: lazy(() => import("./pages/update_flat_data_with_immer.tsx")),
20+
update_tree_data_with_immer: lazy(() => import("./pages/update_tree_data_with_immer.tsx")),
21+
customize_placeholder_and_node_box: lazy(() => import("./pages/customize_placeholder_and_node_box.tsx")),
22+
draggable_droppable: lazy(() => import("./pages/draggable_droppable.tsx")),
23+
dragopen: lazy(() => import("./pages/dragopen.tsx")),
24+
external_drag: lazy(() => import("./pages/external_drag.tsx")),
25+
virtual_list: lazy(() => import("./pages/virtual_list.tsx")),
1526
}
1627
const router = createHashRouter([
1728
{
@@ -29,7 +40,51 @@ const router = createHashRouter([
2940
{
3041
path: "/base_flat_data",
3142
element: <Pages.base_flat_data />,
32-
}
43+
},
44+
{
45+
path: "/custom_drag_trigger_flat_data",
46+
element: <Pages.custom_drag_trigger_flat_data />,
47+
},
48+
{
49+
path: "/open_ids",
50+
element: <Pages.open_ids />,
51+
},
52+
{
53+
path: "/checked_ids",
54+
element: <Pages.checked_ids />,
55+
},
56+
{
57+
path: "/update_data",
58+
element: <Pages.update_data />,
59+
},
60+
{
61+
path: "/update_flat_data_with_immer",
62+
element: <Pages.update_flat_data_with_immer />,
63+
},
64+
{
65+
path: "/update_tree_data_with_immer",
66+
element: <Pages.update_tree_data_with_immer />,
67+
},
68+
{
69+
path: "/customize_placeholder_and_node_box",
70+
element: <Pages.customize_placeholder_and_node_box />,
71+
},
72+
{
73+
path: "/draggable_droppable",
74+
element: <Pages.draggable_droppable />,
75+
},
76+
{
77+
path: "/dragopen",
78+
element: <Pages.dragopen />,
79+
},
80+
{
81+
path: "/external_drag",
82+
element: <Pages.external_drag />,
83+
},
84+
{
85+
path: "/virtual_list",
86+
element: <Pages.virtual_list />,
87+
},
3388
]
3489
},
3590
]);

0 commit comments

Comments
 (0)