浏览代码

refactor(tree): 完善泛型

vian 4 年之前
父节点
当前提交
f8747be1e8
共有 2 个文件被更改,包括 86 次插入87 次删除
  1. 21 21
      tree/src/nodeCtx.ts
  2. 65 66
      tree/src/tree.ts

+ 21 - 21
tree/src/nodeCtx.ts

@@ -1,15 +1,15 @@
-import { TreeNode, Tree } from './tree';
+import { TreeRaw, TreeNode, Tree } from './tree';
 
-export class NodeContext<T = any> {
+export class NodeContext<T extends TreeRaw = TreeRaw> {
   // 对树节点数据的引用
-  ref: TreeNode & T;
+  ref: TreeNode<T>;
 
-  tree: Tree;
+  tree: Tree<T>;
 
   // 展开收起
   expanded = false;
 
-  constructor(node: TreeNode & T, tree: Tree) {
+  constructor(node: TreeNode<T>, tree: Tree<T>) {
     this.ref = node;
     this.tree = tree;
   }
@@ -67,28 +67,28 @@ export class NodeContext<T = any> {
     return true;
   }
 
-  parent(): (TreeNode & T) | null {
+  parent(): TreeNode<T> | null {
     return this.tree.findParent(this.ID());
   }
 
-  next(): (TreeNode & T) | null {
+  next(): TreeNode<T> | null {
     return this.tree.findNext(this.ID());
   }
 
-  prev(): (TreeNode & T) | null {
+  prev(): TreeNode<T> | null {
     return this.tree.findPrev(this.ID());
   }
 
   // 获取节点子项
-  children(): (TreeNode & T)[] {
+  children(): TreeNode<T>[] {
     return this.tree.parentMap[this.ID()] || [];
   }
 
-  firstChild(): (TreeNode & T) | null {
+  firstChild(): TreeNode<T> | null {
     return this.children()[0] || null;
   }
 
-  lastChild(): (TreeNode & T) | null {
+  lastChild(): TreeNode<T> | null {
     const children = this.children();
     return children[children.length - 1] || null;
   }
@@ -100,9 +100,9 @@ export class NodeContext<T = any> {
   }
 
   // 获取节点后代(包含嵌套子项)
-  posterity(): (TreeNode & T)[] {
-    const posterity: (TreeNode & T)[] = [];
-    const getChild = (nodes: (TreeNode & T)[]): void => {
+  posterity(): TreeNode<T>[] {
+    const posterity: TreeNode<T>[] = [];
+    const getChild = (nodes: TreeNode<T>[]): void => {
       nodes.forEach(node => {
         posterity.push(node);
         const children = node.getCtx().children();
@@ -120,20 +120,20 @@ export class NodeContext<T = any> {
   }
 
   // 获取节点的起源节点(根节点,若该节点已为根节点,则返回自身)
-  progenitor(): TreeNode & T {
+  progenitor(): TreeNode<T> {
     let parent = this.parent();
     if (!parent) {
       return this.ref;
     }
     while (parent && parent.getCtx().parent()) {
-      parent = parent.getCtx().parent() as TreeNode & T;
+      parent = parent.getCtx().parent() as TreeNode<T>;
     }
     return parent;
   }
 
   // 获取节点所有先代(包含嵌套父项)
-  ancestor(): (TreeNode & T)[] {
-    const ancestor: (TreeNode & T)[] = [];
+  ancestor(): TreeNode<T>[] {
+    const ancestor: TreeNode<T>[] = [];
     let parent = this.parent();
     while (parent) {
       ancestor.push(parent);
@@ -147,7 +147,7 @@ export class NodeContext<T = any> {
   }
 
   // 获取同层节点
-  brothers(includeSelf = true): (TreeNode & T)[] {
+  brothers(includeSelf = true): TreeNode<T>[] {
     let nodes = this.tree.parentMap[this.parentID()] || [];
     if (!includeSelf) {
       nodes = nodes.filter(node => node.ID !== this.ref.ID);
@@ -160,7 +160,7 @@ export class NodeContext<T = any> {
   }
 
   // 获取后兄弟节点们
-  nextBrothers(): (TreeNode & T)[] {
+  nextBrothers(): TreeNode<T>[] {
     const nodes = this.tree.parentMap[this.parentID()] || [];
     return nodes.filter(
       node => node.seq >= this.seq() && node.ID !== this.ref.ID
@@ -168,7 +168,7 @@ export class NodeContext<T = any> {
   }
 
   // 获取前兄弟节点们
-  prevBrothers(): (TreeNode & T)[] {
+  prevBrothers(): TreeNode<T>[] {
     const nodes = this.tree.parentMap[this.parentID()] || [];
     return nodes.filter(
       node => node.seq <= this.seq() && node.ID !== this.ref.ID

+ 65 - 66
tree/src/tree.ts

@@ -6,25 +6,26 @@ export interface TreeRaw {
   seq: number;
   [props: string]: any;
 }
-
-export interface TreeNode extends TreeRaw {
-  getCtx: () => NodeContext; // ctx对象改为getCtx(),因为handsontable数据循环引用会报错
+interface Node<T extends TreeRaw = TreeRaw> extends TreeRaw {
+  getCtx: () => NodeContext<T>; // ctx对象改为getCtx(),因为handsontable数据循环引用会报错
 }
 
-export interface TreeIDMap<T = any> {
-  [propName: string]: TreeNode & T;
+export type TreeNode<T extends TreeRaw = TreeRaw> = Node<T> & T;
+
+export interface TreeIDMap<T extends TreeRaw = TreeRaw> {
+  [propName: string]: TreeNode<T>;
 }
 
-export interface CtxIDMap<T = any> {
+export interface CtxIDMap<T extends TreeRaw = TreeRaw> {
   [propName: string]: NodeContext<T>;
 }
 
-export interface TreeParentMap<T = any> {
-  [propName: string]: (TreeNode & T)[];
+export interface TreeParentMap<T extends TreeRaw = TreeRaw> {
+  [propName: string]: TreeNode<T>[];
 }
 
-export interface ParentMap<T = any> {
-  [propName: string]: ((TreeRaw | TreeNode) & T)[];
+export interface ParentMap<T extends TreeRaw = TreeRaw> {
+  [propName: string]: (TreeRaw | TreeNode<T>)[];
 }
 
 export interface UpdateItem {
@@ -40,12 +41,12 @@ export interface UpdateData {
 
 export type None = null | undefined;
 
-export class Tree<T = any> {
+export class Tree<T extends TreeRaw = TreeRaw> {
   // 原始数据,不经过排序的数据
-  rawData: (TreeNode & T)[];
+  rawData: TreeNode<T>[];
 
   // 按照树结构拼装好、排好序的数据。实际只是原始数据进行排序,内部元素跟原始数据内部元素的引用是一致的
-  data: (TreeNode & T)[];
+  data: TreeNode<T>[];
 
   rootID: string;
 
@@ -64,7 +65,7 @@ export class Tree<T = any> {
   constructor(rawData: TreeRaw[], rootID = '-1') {
     this.rootID = rootID;
     this.rawData = this.genNodeContext(rawData);
-    this.rawData = Tree.sort(this.rawData);
+    this.rawData = this.sort(this.rawData);
     this.data = [];
     this.IDMap = {};
     this.parentMap = {};
@@ -74,27 +75,27 @@ export class Tree<T = any> {
   }
 
   // 根据seq进行排序
-  static sort<U extends TreeNode | TreeRaw>(nodes: U[]): U[] {
+  sort(nodes: TreeNode<T>[]): TreeNode<T>[] {
     return nodes.sort((a, b) => a.seq - b.seq);
   }
 
   // 获取节点的所有parentID
-  static getParentIDList(nodes: TreeNode[]): Set<string> {
+  getParentIDList(nodes: TreeNode<T>[]): Set<string> {
     const parentIDList: Set<string> = new Set();
     nodes.forEach(node => parentIDList.add(node.parentID));
     return parentIDList;
   }
 
   // 生成节点getCtx方法(由于handsontable循环引用bug,将ctx对象改为getCtx方法),用于获取节点上下文,挂载相关节点方法,TreeRaw转换为TreeNode
-  private genNodeContext(rawData: TreeRaw[]): (TreeNode & T)[] {
+  private genNodeContext(rawData: TreeRaw[]): TreeNode<T>[] {
     rawData.forEach(item => {
       item.getCtx = () => this.ctxMap[item.ID];
     });
-    return rawData as (TreeNode & T)[];
+    return rawData as TreeNode<T>[];
   }
 
   // 生成映射表
-  private genMap(data: (TreeNode & T)[]): void {
+  private genMap(data: TreeNode<T>[]): void {
     data.forEach(item => {
       this.IDMap[item.ID] = item;
       this.ctxMap[item.ID] = new NodeContext(item, this);
@@ -105,7 +106,7 @@ export class Tree<T = any> {
   }
 
   // 获取顶层原始数据
-  getRoots(): (TreeNode & T)[] {
+  getRoots(): TreeNode<T>[] {
     return this.parentMap[this.rootID] || [];
   }
 
@@ -113,7 +114,7 @@ export class Tree<T = any> {
   private genData(): void {
     // genData时不需要排序,因为rawData已经排好序
     const roots = this.getRoots();
-    const pushNodesToData = (nodes: (TreeNode & T)[]): void => {
+    const pushNodesToData = (nodes: TreeNode<T>[]): void => {
       nodes.forEach(node => {
         this.data.push(node);
         const children = this.parentMap[node.ID];
@@ -132,11 +133,11 @@ export class Tree<T = any> {
   }
 
   // 将相关parentMap内的数据、data进行重新排序。新增、删除等操作进行完后,数据需要重新排序
-  reSortData(nodes: (TreeNode & T)[]): void {
-    const toSortList = Tree.getParentIDList(nodes);
+  reSortData(nodes: TreeNode<T>[]): void {
+    const toSortList = this.getParentIDList(nodes);
     toSortList.forEach(parentID => {
       if (this.parentMap[parentID] && this.parentMap[parentID].length) {
-        Tree.sort(this.parentMap[parentID]);
+        this.sort(this.parentMap[parentID]);
       }
     });
     this.reGenData();
@@ -146,19 +147,19 @@ export class Tree<T = any> {
   resortDataByID(parentIDList: string[]): void {
     parentIDList.forEach(parentID => {
       if (this.parentMap[parentID] && this.parentMap[parentID].length) {
-        Tree.sort(this.parentMap[parentID]);
+        this.sort(this.parentMap[parentID]);
       }
     });
     this.reGenData();
   }
 
   // 查找ID节点
-  find(ID: string): (TreeNode & T) | null {
+  find(ID: string): TreeNode<T> | null {
     return this.IDMap[ID] || null;
   }
 
   // 查找ID节点的父节点
-  findParent(ID: string): (TreeNode & T) | null {
+  findParent(ID: string): TreeNode<T> | null {
     const node = this.find(ID);
     if (!node) {
       return null;
@@ -167,7 +168,7 @@ export class Tree<T = any> {
   }
 
   // 查找ID节点的下一个节点
-  findNext(ID: string): (TreeNode & T) | null {
+  findNext(ID: string): TreeNode<T> | null {
     const node = this.find(ID);
     if (!node) {
       return null;
@@ -181,7 +182,7 @@ export class Tree<T = any> {
   }
 
   // 查找ID节点的上一个节点
-  findPrev(ID: string): (TreeNode & T) | null {
+  findPrev(ID: string): TreeNode<T> | null {
     const node = this.find(ID);
     if (!node) {
       return null;
@@ -195,7 +196,7 @@ export class Tree<T = any> {
   }
 
   // 查询ID节点的子节点
-  findChildren(ID: string): (TreeNode & T)[] {
+  findChildren(ID: string): TreeNode<T>[] {
     return this.parentMap[ID] || [];
   }
 
@@ -212,11 +213,11 @@ export class Tree<T = any> {
 
   // 递归获取节点
   getNodesPosterity(
-    treeNodes: (TreeNode & T)[],
+    treeNodes: TreeNode<T>[],
     includeSelf = true
-  ): (TreeNode & T)[] {
-    const rst: (TreeNode & T)[] = [];
-    const pushNodes = (nodes: (TreeNode & T)[]): void => {
+  ): TreeNode<T>[] {
+    const rst: TreeNode<T>[] = [];
+    const pushNodes = (nodes: TreeNode<T>[]): void => {
       nodes.forEach(node => {
         if (includeSelf || !treeNodes.includes(node)) {
           rst.push(node);
@@ -233,7 +234,7 @@ export class Tree<T = any> {
 
   // 节选中点块是否是可操作的节点块,选中一批节点进行升降、上下移的时候可能需要用到这个判断。
   // 以第一个节点为基准,如果后续的节点深度均大于于于等于第一个节点的深度,且节点间是连续的,则此为可操作节点块
-  isOperable(nodes: (TreeNode & T)[]): boolean {
+  isOperable(nodes: TreeNode<T>[]): boolean {
     const baseDepth = nodes[0].getCtx().depth();
     for (let i = 0; i < nodes.length; i++) {
       const node = nodes[i];
@@ -253,7 +254,7 @@ export class Tree<T = any> {
   }
 
   // 从节点块中获取相同深度的节点
-  sameDepthNodes(nodes: (TreeNode & T)[], depth?: number): (TreeNode & T)[] {
+  sameDepthNodes(nodes: TreeNode<T>[], depth?: number): TreeNode<T>[] {
     if (!depth) {
       depth = nodes[0].getCtx().depth();
     }
@@ -264,7 +265,7 @@ export class Tree<T = any> {
   // 可调用完此方法后,将需要更新、插入的数据提交至数据库,成功响应后调用插入节点更新缓存的方法
   prepareInsert(rawData: TreeRaw[]): UpdateData[] {
     const updateData: UpdateData[] = [];
-    const insertParentMap: ParentMap = {};
+    const insertParentMap: ParentMap<T> = {};
     // 将相同父项的插入数据和已存在数据进行合并
     rawData.forEach(item => {
       if (typeof item.seq === 'undefined') {
@@ -274,7 +275,7 @@ export class Tree<T = any> {
         insertParentMap[item.parentID] || (insertParentMap[item.parentID] = [])
       ).push(item);
     });
-    const combineMap: ParentMap = {};
+    const combineMap: ParentMap<T> = {};
     Object.entries(insertParentMap).forEach(([parentID, insertItems]) => {
       const items = this.parentMap[parentID];
       combineMap[parentID] = [...insertParentMap[parentID]];
@@ -283,7 +284,7 @@ export class Tree<T = any> {
       }
       // 重新排序
       const combineItems = combineMap[parentID];
-      Tree.sort(combineItems);
+      this.sort(combineItems as any);
       combineItems.forEach((item, index) => {
         // 插入数据重新赋值
         if (insertItems.includes(item)) {
@@ -303,7 +304,7 @@ export class Tree<T = any> {
   }
 
   // 插入节点数据
-  insert(items: TreeRaw[], updateData: UpdateData[] = []): (TreeNode & T)[] {
+  insert(items: TreeRaw[], updateData: UpdateData[] = []): TreeNode<T>[] {
     // 更新需要更新的节点(一般为更新seq)
     this.updateValue(updateData);
     // 建立映射、插入数据
@@ -316,21 +317,20 @@ export class Tree<T = any> {
   }
 
   // 准备删除,返回所有需要删除的节点,包括嵌套节点
-  prepareDelete(deleteNodes: (TreeNode & T)[]): (TreeNode & T)[] {
+  prepareDelete(deleteNodes: TreeNode<T>[]): TreeNode<T>[] {
     return this.getNodesPosterity(deleteNodes);
   }
 
   /**
    * 删除节点
-   * @param {(TreeNode & T)[]} treeNodes - 要删除的节点,不需要包含嵌套节点
-   * @return {(TreeNode & T)[]} 返回被删除的节点
+   * @param treeNodes - 要删除的节点,不需要包含嵌套节点
    */
-  delete(treeNodes: (TreeNode & T)[]): (TreeNode & T)[] {
-    const allDeletedNodes: (TreeNode & T)[] = [];
+  delete(treeNodes: TreeNode<T>[]): TreeNode<T>[] {
+    const allDeletedNodes: TreeNode<T>[] = [];
     // 递归删除节点
-    const deleteNodes = (nodes: (TreeNode & T)[]): void => {
+    const deleteNodes = (nodes: TreeNode<T>[]): void => {
       // 删除映射、删除数据
-      const toDels: { nodes: TreeNode[]; delNode: TreeNode }[] = [];
+      const toDels: { nodes: TreeNode<T>[]; delNode: TreeNode<T> }[] = [];
       nodes.forEach(node => {
         allDeletedNodes.push(node);
         delete this.IDMap[node.ID];
@@ -371,8 +371,8 @@ export class Tree<T = any> {
   }
 
   // IDList 返回所有需要删除的节点,包括嵌套节点
-  prepareDeleteByID(IDList: string[]): (TreeNode & T)[] {
-    const deleteNodes: (TreeNode & T)[] = [];
+  prepareDeleteByID(IDList: string[]): TreeNode<T>[] {
+    const deleteNodes: TreeNode<T>[] = [];
     IDList.forEach(ID => {
       const node = this.find(ID);
       if (node) {
@@ -384,11 +384,10 @@ export class Tree<T = any> {
 
   /**
    * 根据ID删除节点
-   * @param {string[]} IDList - 要删除的节点的ID列表(不包含嵌套节点ID)
-   * @return {TreeNode[]} - 返回被删除的所有节点
+   * @param IDList - 要删除的节点的ID列表(不包含嵌套节点ID)
    */
-  deleteByID(IDList: string[]): (TreeNode & T)[] {
-    const deleteNodes: (TreeNode & T)[] = [];
+  deleteByID(IDList: string[]): TreeNode<T>[] {
+    const deleteNodes: TreeNode<T>[] = [];
     IDList.forEach(ID => {
       const node = this.find(ID);
       if (node) {
@@ -400,7 +399,7 @@ export class Tree<T = any> {
   }
 
   // 准备上移节点块(连续的兄弟节点),注意节点的seq可能不连号
-  prepareUpMove(nodes: (TreeNode & T)[]): UpdateData[] {
+  prepareUpMove(nodes: TreeNode<T>[]): UpdateData[] {
     const updateData: UpdateData[] = [];
     const firstNode = nodes[0];
     const firstNodePrev = this.findPrev(firstNode.ID);
@@ -424,7 +423,7 @@ export class Tree<T = any> {
   }
 
   // 准备下移节点块(连续的兄弟节点),注意节点的seq可能不连号
-  prepareDownMove(nodes: (TreeNode & T)[]): UpdateData[] {
+  prepareDownMove(nodes: TreeNode<T>[]): UpdateData[] {
     const updateData: UpdateData[] = [];
     const lastNode = nodes[nodes.length - 1];
     const lastNodeNext = this.findNext(lastNode.ID);
@@ -449,13 +448,13 @@ export class Tree<T = any> {
   }
 
   // 上下移
-  move(nodes: (TreeNode & T)[], updateData: UpdateData[]): void {
+  move(nodes: TreeNode<T>[], updateData: UpdateData[]): void {
     this.updateValue(updateData);
     this.reSortData(nodes);
   }
 
   // 准备升级节点块(连续的兄弟节点),不维护seq连号
-  prepareUpLevel(nodes: (TreeNode & T)[]): UpdateData[] {
+  prepareUpLevel(nodes: TreeNode<T>[]): UpdateData[] {
     const updateData: UpdateData[] = [];
     const firstNode = nodes[0];
     const lastNode = nodes[nodes.length - 1];
@@ -495,7 +494,7 @@ export class Tree<T = any> {
     return updateData;
   }
 
-  upLevel(nodes: (TreeNode & T)[], updateData: UpdateData[]): void {
+  upLevel(nodes: TreeNode<T>[], updateData: UpdateData[]): void {
     const firstNode = nodes[0];
     const lastNode = nodes[nodes.length - 1];
     if (!firstNode.getCtx().canUpLevel()) {
@@ -516,7 +515,7 @@ export class Tree<T = any> {
   }
 
   // 准备降级节点块(连续的兄弟节点),不维护seq连号
-  prepareDownLevel(nodes: (TreeNode & T)[]): UpdateData[] {
+  prepareDownLevel(nodes: TreeNode<T>[]): UpdateData[] {
     const updateData: UpdateData[] = [];
     const firstNode = nodes[0];
     const prevNode = this.findPrev(firstNode.ID);
@@ -537,7 +536,7 @@ export class Tree<T = any> {
     return updateData;
   }
 
-  downLevel(nodes: (TreeNode & T)[], updateData: UpdateData[]): void {
+  downLevel(nodes: TreeNode<T>[], updateData: UpdateData[]): void {
     const firstNode = nodes[0];
     if (!firstNode.getCtx().canDownLevel()) {
       return;
@@ -558,12 +557,12 @@ export class Tree<T = any> {
 
   // 准备移动节点块(连续的兄弟节点),不维护seq连号
   prepareMoveTo(
-    nodes: (TreeNode & T)[],
-    parent: (TreeNode & T) | None,
-    next: (TreeNode & T) | None
+    nodes: TreeNode<T>[],
+    parent: TreeNode<T> | None,
+    next: TreeNode<T> | None
   ): UpdateData[] {
     const updateData: UpdateData[] = [];
-    let prev: (TreeNode & T) | null;
+    let prev: TreeNode<T> | null;
     if (next) {
       prev = next.getCtx().prev();
     } else {
@@ -601,8 +600,8 @@ export class Tree<T = any> {
   }
 
   moveTo(
-    nodes: (TreeNode & T)[],
-    parent: (TreeNode & T) | None,
+    nodes: TreeNode<T>[],
+    parent: TreeNode<T> | None,
     updateData: UpdateData[]
   ): void {
     const firstNode = nodes[0];