2024-01-29 09:26:07 +08:00

403 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 请修改 docs/zh-CN.md项目根目录中的 index.zh-CN.md 只是一个副本 -->
# treemate · [![Coverage Status](https://coveralls.io/repos/github/07akioni/treemate/badge.svg)](https://coveralls.io/github/07akioni/treemate)
https://treemate.vercel.app/zh-CN.html
组件开发中树形数据结构的一站式解决方案。
帮助人们操作树形数据结构(可以用于 TreeSelectDropdownTableMenu 等组件)。
1. 勾选节点,取消勾选节点
2. 展开节点,折叠节点
3. 在节点中移动
4. 获取打平为数组的树
5. 查询节点
6. 支持 group 节点(聚集了一些同层级节点的节点)
7. 支持 ignored 节点(在移动时忽略)
8. 节点的元信息
9. 获取到对应的原始数据引用
10. 不完整数据中的勾选
11. ...
## 安装
```bash
npm i -D treemate
```
## 基本概念
在开始之前,我强烈建议阅读这一节,了解一些 treemate 的基本概念。
### 普通节点Group 节点和 Ignored 节点
在 treemate 中,一个树由节点(可选的 group 节点和可选的 ignored 节点)组成。
一个普通节点包括一个 key可能还包含一个 children 属性,里面是它的子节点。
#### 普通节点
```
Node {
key,
children?
}
```
#### Group 节点
如果你不需要 Group 节点,可以跨过这一节。
一个 Group 节点包含一个 key一个名为 type 的属性,值为 `group`,还有一个 children 属性包含了它的子节点(不能包含 Group 节点)。
```
GroupNode {
key,
type: 'group',
children
}
```
在不同节点的移动过程中Group 节点自身会被忽略Group 节点的子节点会被看作为和 Group 在同一层的节点。
例如在下面的树中group node 1 是 node 1 的下一个节点group child 2 的下一个节点是 node 2.
```
- node 1
- group node 1
- group child 1
- group child 2
- node 2
```
#### Ignored 节点
有的时候你需要一些仅需渲染的节点。例如
```
- node 1
- 分割线 (render only)
- node 2
```
在数据层面,分割线节点没有实际意义。你可以把它作为一个 Ignored 节点。这个节点在节点移动的过程中会被忽略。node 2 会被看作 node 1 的下一个节点。)同时 `getNode` 方法也不会返回 Ignored 节点。
Ignored 节点也应该包含一个 key为现代前端框架的 diff 过程使用)。
```
IgnoredNode {
key,
type: 'ignored'
}
```
## 使用方式
### 创建一个 Treemate
`createTreeMate` 方法接受一个节点的数组作为输入数据,返回一个 treemate 的实例。
在 javascript 中使用:
```js
import { createTreeMate } from 'treemate'
const data = [
// 非叶节点
{
key: 1,
children: [
{
key: 2
}
]
},
// 叶节点
{
key: 3
},
// ignored 节点
{
key: 4,
type: 'ignored'
},
// group 节点
{
key: 5,
type: 'group',
children: [
{
key: 6
}
]
}
]
const treeMate = createTreeMate(data)
```
在 typescript 中data 没什么区别。但是 `createTreeMate` 接受 3 个可选的泛型参数这些参数为普通节点、Group 节点和 Ignored 节点的类型。
```ts
interface BaseNode {
key: string | number
children?: Array<BaseNode | GroupNode | IgnoredNode>
}
interface GroupNode {
key: string | number
type: 'group'
children: Array<BaseNode | IgnoredNode>
}
interface IgnoredNode {
key: string | number
type: 'ignored'
}
// 1. 指名左右节点的类型
const treeMate = createTreeMate<BaseNode, GroupNode, IgnoredNode>(data)
// 2. 等价于 createTreeMate<BaseNode, GroupNode, BaseNode>()
// 也就是说在数据中不会出现 Ignored 节点
const treeMate = createTreeMate<BaseNode, GroupNode>(data)
// 3. 等价于 createTreeMate<BaseNode, BaseNode, BaseNode>()
// 也就是说 Ignored 节点和 Group 节点不会出现在数据中
const treeMate = createTreeMate<BaseNode>(data)
// 4. 不写任何泛型参数
// 它会使用一个内置的类型作为基本节点的类型
// RawNode {
// key: string | number
// children?: RawNode[]
// disabled?: boolean
// isLeaf? boolean
// }
// Ignored 节点和 Group 节点不会出现在数据中
const treeMate = createTreeMate(data)
```
### 自定义 `createTreeMate` 的选项
如果希望通过其他方式来判定一个节点的 key 和 disabled、group、ignored 的状态,你可以给 create treemate 传递一个选项。
```ts
const treeMate = createTreeMate(data, {
getKey: (node) => Key,
getDisabled: (node) => boolean,
getIsGroup: (node) => boolean,
getIgnored: (node) => boolean
})
```
### 从树中获取一个节点
假设我们有一个 treemate 的实例。
```ts
const tmNode = treeMate.getNode(key) // 如果不存在该节点会返回 null
// 注意getNode 不会返回 Group 节点和 Ignored 节点!
// 如果你需要获取这些节点,你可以使用 treeNodeMap例如
treeMate.treeNodeMap.get(key)
```
### TreeMateNode 树节点的属性
```js
TreeMateNode {
key,
rawNode, // 对于原始数据节点的引用,很可能有用
level, // 从 0 开始
index, // 它在父节点(或者根数组)中的 index
siblings,
isFirstChild,
isLastChild,
parent, // 父树节点(不是数据节点)
isShallowLoaded, // 在局部加载数据时会用到
isLeaf,
isGroup,
ignored, // boolean
disabled, // disabled
children?, // 它的子树节点(不是原始数据节点)
getPrev(), // 方法
getNext(), // 方法
getParent(), // 方法
getChild() // 方法
}
```
### 在树中勾选节点或取消勾选节点
#### `TreeMate.getCheckedKeys(checkedKeys, options?)`
获取树的勾选状态。
`disabled = true` 的节点会阻止关联勾选的检查传播。
参数 `checkedKeys` 有两种形式:
```ts
Key[] // 1. 当前勾选的节点
// 2. 当前的综合勾选状态
interface InputMergedKeys {
checkedKeys?: Key[] | null
indeterminateKeys?: Key[] | null // half checked
}
// 也可以是
null | undefined
// 会被当作空数组看待
```
选项 `options` 形如:
```ts
interface CheckOptions {
cascade?: boolean // 是否关联勾选,子级勾选会影响父级状态,默认为 true
leafOnly?: boolean // 是否只允许叶节点被勾选,默认为 false
checkStrategy?: string // 勾选显示策略 'all' | 'parent' | 'child'
allowNodeLoaded?: boolean // 是否在允许在不加载完全相关节点的情况下进行关联勾选
}
```
返回值形如:
```ts
interface MergedKeys {
checkedKeys: Key[]
indeterminateKeys: Key[] // 半选
}
```
##### 使用举例
```ts
const { checkedKeys, indeterminateKeys } = treeMate.getCheckedKeys([1])
const { checkedKeys, indeterminateKeys } = treeMate.getCheckedKeys([1], {
cascade: false
})
const { checkedKeys, indeterminateKeys } = treeMate.getCheckedKeys({
checkedKeys: [1],
indeterminateKeys: [2]
})
// ...
```
#### `TreeMate.check(keysToCheck, checkedKeys, options?)`
获取树在勾选一些节点后新的勾选状态。
`keysToCheck` 可以为 `Key | Key[] | null | undefined`
`checkedKeys``options` 和返回值参考 `getCheckedKeys(checkedKeys, options?)`
#### `TreeMate.uncheck(keysToUncheck, checkedKeys, options?)`
获取树在取消勾选一些节点后新的勾选状态。
`keysToCheck` 可以为 `Key | Key[] | null | undefined`
`checkedKeys`, `options` 和返回值参考 `getCheckedKeys(checkedKeys, options?)`
### 在树中移动
#### `TreeMate.getPrev(key, options?)`
获取该 `key` 对应节点的前一个非 `disabled``TreeMateNode`,寻找过程中 `group | ignored` 节点自身会被忽略,不存在时返回 `null`
`options` 形如 `{ loop?: boolean }`,默认 `loop``false`,不会循环寻找。
#### `TreeMate.getNext(key, options?)`
获取该 `key` 对应节点的后一个非 `disabled``TreeMateNode`,寻找过程中 `group | ignored` 节点自身会被忽略,不存在时返回 `null`
`options` 形如 `{ loop?: boolean }`,默认 `loop``false`,不会循环寻找。
#### `TreeMate.getParent(key)`
获取该 `key` 对应节点的父级 `TreeMateNode`,寻找过程中 `group` 节点自身会被忽略,不存在时返回 `null`
#### `TreeMate.getChild(key)`
获取该 `key` 对应节点第一个非 `disabled``TreeMateNode`,寻找过程中 `group | ignored` 节点自身会被忽略,不存在时返回 `null`
#### `TreeMateNode.getPrev(options?)`
获取该节点的前一个非 `disabled``TreeMateNode`,寻找过程中 `group | ignored` 节点自身会被忽略,不存在时返回 `null`
`options` 形如 `{ loop?: boolean }`,默认 `loop``false`,不会循环寻找。
#### `TreeMateNode.getNext(options?)`
获取该节点的后一个非 `disabled``TreeMateNode`,寻找过程中 `group | ignored` 节点自身会被忽略,不存在时返回 `null`
`options` 形如 `{ loop?: boolean }`,默认 `loop``false`,不会循环寻找。
#### `TreeMateNode.getParent()`
获取该节点的父级 `TreeMateNode`,寻找过程中 `group` 节点自身会被忽略,不存在时返回 `null`
#### `TreeMateNode.getChild()`
获取该节点第一个非 `disabled``TreeMateNode`,寻找过程中 `group | ignored` 节点自身会被忽略,不存在时返回 `null`
### 展开、折叠树节点
展开状态会影响树的展平状态。展平节点对于虚拟列表至关重要。
#### `TreeMate.getFlattenedNodes(expandedKeys?)`
获取树对应于 `expandedKeys` 的展平节点。如果 `expandedKeys` 没有传入treemate 会当作所有节点全部处于展开状态。
#### `createIndexGetter(flattenedNodes)`
从展平节点创建一个索引的 getter 函数。
```ts
import { createIndexGetter } from 'treemate'
const getIndex = createIndexGetter(flattenedNodes)
getIndex(flattenedNodes[0].key) === 0
```
### 获取节点的路径
#### `TreeMate.getPath(key, options)`
获取从根到该 `key` 对应节点的路径。返回值形如
```ts
interface MergedPath {
keyPath: Key[]
treeNodePath: TreeMateNode[]
treeNode: TreeMateNode | null
}
```
其中 `keyPath` 为路径中各个节点的 `key`。其中 `treeNodePath` 为节点路径。`treeNode``key` 对应的 `TreeMateNode`
`options` 形如 `{ includeSelf?: boolean, includeGroup?: boolean }`,默认 `includeSelf` 为 true`includeGroup` 为 false。
### 获取树第一个可用的节点
可以用于获取选择菜单的默认选项。
#### `TreeMate.getFirstAvailableNode()`
获取整个树第一个非 `disabled``TreeMateNode`,寻找过程中 `group ignored` 节点自身会被忽略,不存在时返回 `null`
### TreeMate 实例的其他属性
#### `TreeMate.treeNodes`
原数据对应的 `TreeMateNode` 数组,结构完全对应于原数据。
#### `TreeMate.treeNodeMap`
`key` 到节点的 Map。包含全部节点`group | ignored` 节点包含在内。