# TreeNode - 树节点数据结构

## 概述

`TreeNode` 是一个类，用于表示树形结构中的节点。它封装了节点的数据、子节点、展开状态，并提供了便捷的方法来操作节点本身和其子节点。

每个从 `TreeNodes` 或 `CommonTreeNodes` 创建的节点都是 `TreeNode` 类的实例，具有内置的方法。

## TreeNode 类

```typescript
class TreeNode<T> implements ITreeNode<T> {
  /** 节点的数据对象（只读） */
  public readonly item: T;

  /** 是否展开（显示子节点） */
  public expand: boolean;

  /** 是否正在加载（用于懒加载） */
  public loading: boolean;

  /** 父节点（根节点为 null） */
  public readonly parent: TreeNode<T> | null;

  /** 节点层级（根节点为 0） */
  get level(): number;

  /** 子节点（浅拷贝，使用方法修改） */
  get children(): Array<TreeNode<T>> | null;

  /** 添加子节点到当前节点 */
  append(childItem: T | Array<T>): ITreeNode<T> | Array<ITreeNode<T>>;

  /** 从父节点中分离（删除当前节点） */
  detach(): void;

  /** 将当前节点移动到另一个父节点下（返回新节点） */
  moveTo(newParent: TreeNode<T>): TreeNode<T>;

  /** 替换当前节点的数据 */
  replace(newItem: T): void;

  /** 删除所有子节点 */
  removeChildren(): void;
}
```

## NodeViewOptions 类

`NodeViewOptions` 类处理树节点的视图配置，将显示关注点与数据分离：

```typescript
class NodeViewOptions<T> {
  /** 唯一标识字段名 */
  keyField: keyof T;

  /** 显示文字的字段名或函数 */
  textField: keyof T | ((data: T) => string);

  /** 可选：图标的字段名或函数 */
  iconField?: keyof T | ((data: T) => string);

  /** 可选：CSS 类名的字段名或函数 */
  cssClassField?: keyof T | ((data: T) => string);

  /** 可选：内联样式的字段名或函数 */
  styleField?: keyof T | ((data: T) => Record<string, string>);

  /** 从数据获取显示文字 */
  getText(data: T): string;

  /** 从数据获取图标 */
  getIcon(data: T): string | undefined;

  /** 从数据获取 CSS 类名 */
  getCssClass(data: T): string | undefined;

  /** 从数据获取样式 */
  getStyle(data: T): Record<string, string> | undefined;

  /** 从数据获取唯一键 */
  getKey(data: T): any;
}
```

## TreeNode 方法详解

### 1. append(childItem)

添加子节点到当前节点。

**参数**:
- `childItem: T | Array<T>` - 子节点的数据对象或子对象数组

**示例**:
```typescript
// 添加单个子节点
parentNode.append({
  id: 123,
  name: "新子节点",
  parentId: parentNode.item.id  // 会自动设置
});

// 添加多个子节点
parentNode.append([
  { id: 124, name: "子节点1", parentId: parentNode.item.id },
  { id: 125, name: "子节点2", parentId: parentNode.item.id }
]);
```

**返回值**:
- `ITreeNode<T> | Array<ITreeNode<T>>` - 添加的子节点

**注意事项**:
- 自动初始化 children 数组（如果需要）
- 如果配置了排序函数，会自动排序
- 触发版本更新以实现响应式更新

---

### 2. detach()

从父节点中分离当前节点。

**参数**: 无

**示例**:
```typescript
// 从父节点中删除该节点
node.detach();

// 删除后清空引用
activeNode.detach();
activeNode = null;
```

**注意事项**:
- 从父节点的 `children` 数组中移除
- 从内部映射表中删除
- 如果是根节点，从根节点列表中移除
- 触发版本更新以实现响应式更新

---

### 3. moveTo(newParent)

将当前节点移动到另一个父节点下。

**参数**:
- `newParent: TreeNode<T>` - 新的父节点（不是 ID）

**示例**:
```typescript
// 移动到另一个父节点
node.moveTo(newParentNode);

// 先获取父节点
const parentNode = treeNodes.nodeMap.get(parentId);
if (parentNode) {
  node.moveTo(parentNode);
}
```

**注意事项**:
- 接收 TreeNode 实例作为参数，不是 ID
- 会自动更新节点数据中的 `parentKeyField`
- 从原父节点的 `children` 中移除
- 添加到新父节点的 `children`
- 如果配置了排序函数，会自动重新排序
- 触发版本更新以实现响应式更新

---

### 4. replace(newItem)

替换当前节点的数据。

**参数**:
- `newItem: T` - 新的数据对象

**示例**:
```typescript
// 更新节点的部分字段
node.replace({
  ...node.item,        // 保持其他字段
  name: "新的名称",     // 更新名称
  status: "active"     // 更新状态
});

// 或完全替换
node.replace({
  id: node.item.id,    // 必须保持 ID
  name: "完全新的数据",
  newField: "值"
});
```

**注意事项**:
- 通常需要保持 `id` 字段不变
- 如果配置了排序函数且排序字段改变，会自动重新排序
- 会自动触发视图更新
- 保留子节点、展开状态和加载状态

---

### 5. removeChildren()

删除所有子节点。

**参数**: 无

**示例**:
```typescript
// 清空所有子节点
parentNode.removeChildren();

// 删除后添加新节点
parentNode.removeChildren();
parentNode.append(newChildData);
```

**注意事项**:
- 会删除所有子节点及其后代
- 节点本身的 `expand` 状态保持不变
- 内部映射表会同步更新
- 触发版本更新以实现响应式更新

## CommonTreeNodes 配置选项

创建树时，可以使用以下选项进行配置：

```typescript
interface TreeNodeOptions<T> {
  /** 唯一标识字段名（默认：'id'） */
  keyField?: keyof T;

  /** 显示文字的字段名或函数（默认：'text'） */
  textField?: keyof T | ((data: T) => string);

  /** 父节点引用字段名（默认：'parentId'） */
  parentKeyField?: keyof T;

  /** 判断数据是否为根节点的函数 */
  checkIsRoot: (data: T) => boolean;

  /** 可选：判断节点是否为分支（有子节点）的函数 */
  checkIsDirectory?: (node: TreeNode<T>) => boolean;

  /** 可选：排序比较函数 */
  compareFun?: (o1: T, o2: T) => number | undefined;

  /** 可选：展开深度（默认：1） */
  expendDepth?: number;
}
```

## 完整使用示例

```svelte
<script>
  import TreeView from "@ticatec/uniface-element/TreeView";
  import { CommonTreeNodes, type TreeNode } from "@ticatec/uniface-element/lib/TreeNodes";

  // 数据类型定义
  interface MyData {
    id: number;
    name: string;
    parentId: number | null;
  }

  let activeNode: TreeNode<MyData> | null = null;

  // 创建树结构
  const treeNodes = new CommonTreeNodes<MyData>({
    keyField: 'id',
    textField: 'name',
    parentKeyField: 'parentId',
    checkIsRoot: (item) => item.parentId === null,
    checkIsDirectory: (node) => node.children.length > 0,
    expendDepth: 2
  });

  // 初始化数据
  treeNodes.setData([
    { id: 1, name: "根节点", parentId: null },
    { id: 2, name: "子节点1", parentId: 1 },
    { id: 3, name: "子节点2", parentId: 1 }
  ]);

  // 添加子节点
  function addChild() {
    if (!activeNode) return;

    activeNode.append({
      id: Date.now(),
      name: "新子节点",
      parentId: activeNode.item.id
    });
  }

  // 删除当前节点
  function deleteCurrentNode() {
    if (!activeNode) return;
    activeNode.detach();
    activeNode = null;
  }

  // 删除指定子节点
  function deleteChild(childId: number) {
    if (!activeNode) return;
    activeNode.removeChild(childId);
  }

  // 清空所有子节点
  function clearChildren() {
    if (!activeNode) return;
    activeNode.removeChildren();
  }

  // 重命名节点
  function renameNode(newName: string) {
    if (!activeNode) return;
    activeNode.replace({
      ...activeNode.item,
      name: newName
    });
  }

  // 移动节点
  function moveNodeTo(newParentId: number) {
    if (!activeNode) return;
    const newParent = treeNodes.nodeMap.get(newParentId);
    if (newParent) {
      activeNode.moveTo(newParent);
    }
  }
</script>

<div>
  <TreeView
    nodes={treeNodes.nodes}
    version={treeNodes.version}
    textField="name"
    bind:activeNode
    checkIsDirectory={(node) => node.children.length > 0}
  />

  {#if activeNode}
    <div class="node-actions">
      <h3>当前选中: {activeNode.item.name}</h3>
      <button on:click={addChild}>添加子节点</button>
      <button on:click={deleteCurrentNode}>删除节点</button>
      <button on:click={() => renameNode("新名称")}>重命名</button>
      <button on:click={() => moveNodeTo(1)}>移动到根节点</button>
      <button on:click={clearChildren}>清空子节点</button>
    </div>
  {/if}
</div>
```

## 与 TreeNodes 类方法的区别

`TreeNode` 实例方法和 `CommonTreeNodes`/`TreeNodes` 类方法有明确的职责分工：

### TreeNode 实例方法（用于操作特定节点实例）

```typescript
// 直接在节点实例上操作
node.append(childItem);      // 添加子节点到该节点
node.detach();               // 从父节点中分离该节点
node.replace(newItem);       // 替换该节点的数据
node.moveTo(newParentNode);  // 将该节点移动到另一个父节点
node.removeChildren();       // 删除该节点的所有子节点
```

### CommonTreeNodes/TreeNodes 类方法（用于树级别的操作）

```typescript
// 树级别操作
treeNodes.setData(data);           // 初始化树结构
treeNodes.append(item);            // 添加新节点（自动找父节点）
treeNodes.nodes;                   // 获取根节点列表
treeNodes.version;                 // 获取版本号
treeNodes.getHierarchyList();      // 获取展开的节点列表（仅 TreeNodes）
treeNodes.extractDirectories(item); // 提取目录结构（仅 TreeNodes）
```

## 主要属性

### item（只读）
节点中存储的数据对象。不能直接修改。使用 `replace()` 来更新数据。

### children（只读）
返回子节点数组的浅拷贝。要修改子节点，使用以下方法：
- `append()` - 添加子节点
- `removeChildren()` - 删除所有子节点
- 要删除特定子节点，请遍历子节点并对子节点调用 `detach()` 方法

### expand
控制节点是否展开（显示其子节点）。可以直接设置：
```typescript
node.expand = true;   // 展开节点
node.expand = false;  // 折叠节点
```

### loading
指示节点是否正在加载数据（用于懒加载）。可以直接设置：
```typescript
node.loading = true;  // 显示加载状态
node.loading = false; // 隐藏加载状态
```

### parent
父节点的引用。根节点为 `null`。

```typescript
if (node.parent) {
  console.log("父节点名称:", node.parent.item.name);
}
```

## 响应式更新

所有的节点方法操作都会自动触发 `version` 更新，确保 Svelte 组件能够响应数据变化：

```svelte
<TreeView
  nodes={treeNodes.nodes}
  version={treeNodes.version}  ← 自动更新
  ...
/>
```

你不需要手动触发更新，节点方法会自动处理。

## 常见问题

### Q: 如何获取节点实例？

```typescript
// 方式1: 通过 activeNode（选中节点）
let activeNode: TreeNode | null = null;
<TreeView bind:activeNode />

// 方式2: 通过 nodeMap（需要保留 TreeNodes 引用）
const node = treeNodes.nodeMap.get(nodeId);
```

### Q: 如何遍历子节点？

```typescript
// children 是只读的，但可以迭代
node.children.forEach(child => {
  console.log(child.item.name);
});

// 或使用 for...of
for (const child of node.children) {
  console.log(child.item.name);
}
```

### Q: 如何查找特定子节点？

```typescript
function findChild(node: TreeNode, childId: number) {
  return node.children.find(child => child.item.id === childId) || null;
}
```

### Q: 可以直接修改 children 数组吗？

不可以。`children` 属性是只读的。请使用提供的方法：
- `append()` 添加
- `removeChild()` 删除特定子节点
- `removeChildren()` 删除所有子节点

### Q: 如何递归操作所有子孙节点？

```typescript
function traverse(node: TreeNode, callback: (node: TreeNode) => void) {
  callback(node);
  for (const child of node.children) {
    traverse(child, callback);
  }
}

// 使用
traverse(rootNode, (node) => {
  console.log(node.item.name);
  node.expand = true;  // 展开所有节点
});
```

### Q: TreeNode 和 NodeViewOptions 有什么区别？

- **TreeNode**：表示数据结构。每个节点都是具有操作方法的实例。
- **NodeViewOptions**：配置类，用于定义节点如何显示（文字字段、图标、CSS 等）。

这种分离使得 TreeNode 可以在不同上下文中使用（TreeView、TreeDataGrid 等），而不会与视图特定关注点耦合。

## 最佳实践

1. **优先使用节点方法**：当你有节点实例时，直接调用节点方法更直观

2. **保持 ID 不变**：使用 `replace()` 时，确保 `id` 字段保持不变

3. **正确使用 moveTo**：`moveTo()` 方法接收 TreeNode 实例，不是 ID：
   ```typescript
   // ❌ 错误
   node.moveTo(123);

   // ✅ 正确
   const parentNode = treeNodes.nodeMap.get(123);
   node.moveTo(parentNode);
   ```

4. **版本管理**：不需要手动管理版本，节点方法会自动更新

5. **类型安全**：使用 TypeScript 泛型确保类型安全

```typescript
// ✅ 推荐：使用泛型
const treeNodes = new CommonTreeNodes<MyData>({ ... });

// 节点类型会自动推断
node.append({ id: 1, name: "..." });  // 类型检查
```

6. **children 是只读的**：不要尝试直接修改 children 数组。始终使用提供的方法。

## 性能考虑

- 所有节点方法的时间复杂度都是 O(1) 或 O(n)，其中 n 是子节点数量
- 删除操作会同时从内部映射表中移除，保持一致性
- 大批量操作时，考虑直接使用 `setData()` 重建树结构

## 架构设计

### 与视图解耦

TreeNode 设计为独立于任何特定视图组件：
- TreeNode 中没有视图特定的逻辑
- NodeViewOptions 单独处理视图配置
- 可用于 TreeView、TreeDataGrid 或任何其他基于树的组件

### 数据不可变性

`item` 属性是只读的，以确保数据完整性：
- 防止意外修改
- 通过 `replace()` 方法使状态更改显式化
- 有助于调试和状态跟踪

## 相关组件

- `CommonTreeNodes` - 基础树管理类
- `TreeNodes` - 扩展的树管理类，具有层次结构功能
- `TreeView` - 树视图组件
- `NodeViewOptions` - 视图配置类