JavaScript ツリー配列のフラット化/およびフラット化された配列をツリー配列に復元する

フロントエンド データの要件については、1 つはツリー配列を平坦化すること、もう 1 つは平坦化された配列をツリー配列として出力することです。
相互変換を実現するには次の 2 つの方法があります。
ツリー配列は次のとおりです。

let tree = [
    {
    
    
        "id": 1,
        "name": "1",
        "pid": 0,
        "children": [
            {
    
    
                "id": 2,
                "name": "2",
                "pid": 1,
                "children": []
            },
            {
    
    
                "id": 3,
                "name": "3",
                "pid": 1,
                "children": [
                   {
    
    
                     "id": 4,
                     "name": "4",
                     "pid": 3,
                     "children": []
                   }
                ]
            }
        ]
    }
]

平坦化された配列は次のとおりです。

let arr = [
 {
    
    id: 1, name: '1', pid: 0},
 {
    
    id: 2, name: '2', pid: 1},
 {
    
    id: 3, name: '3', pid: 1},
 {
    
    id: 4, name: '4', pid: 3},
 {
    
    id: 5, name: '5', pid: 3},
]

1. まず、ツリー配列を平坦化します。

// reduce实现
function treeToArray(tree) {
    
    
  return tree.reduce((res, item) => {
    
    
    const {
    
     children, ...i } = item
    return res.concat(i, children && children.length ? treeToArray(children) : [])
  }, [])
}

2. フラット化された配列をツリーに変換する

// 先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储
function arrayToTree(items) {
    
    
  const result = [];   // 存放结果集
  const itemMap = {
    
    };  // 
  for (const item of items) {
    
    
    const id = item.id;
    const pid = item.pid;

    if (!itemMap[id]) {
    
    
      itemMap[id] = {
    
    
        children: [],
      }
    }

    itemMap[id] = {
    
    
      ...item,
      children: itemMap[id]['children']
    }

    const treeItem =  itemMap[id];

    if (pid === 0) {
    
    
      result.push(treeItem);
    } else {
    
    
      if (!itemMap[pid]) {
    
    
        itemMap[pid] = {
    
    
          children: [],
        }
      }
      itemMap[pid].children.push(treeItem)
    }

  }
  return result;
}

3. 特殊なケース 1 – 各配列のフィールド名のキー値が異なる

次のメソッドはジェネレーター関数に基づいています。

//flattenTreeGenerator
ノードと子ノードのキー名を含む配列をパラメータとして受け取り、ジェネレータ オブジェクトを返すジェネレータ関数を定義します。ジェネレーターが反復されると、ツリー構造全体が走査されるまで、ノードが深さ優先の順序で出力されます。
このメソッドでは、ジェネレーター関数flattenTreeを呼び出してflattenTreeGeneratorツリー構造を平坦化し、結果をflattenedTree配列に保存します。さらに*、関数をジェネレーター関数として定義します。
ジェネレーター関数は、実行時に実行を一時停止し、いつでも実行を再開できる特別な関数です。ジェネレーター関数は、キーワードを使用してyield実行フローを制御し、next()メソッドを通じて続行できます。
ジェネレーター関数の実行が終了すると、出力値を調べるために使用されるイテレーター (反復子) が自動的に返されます。
ツリー構造を平坦化するプロセスでは、ツリーの各ノードを再帰的に走査する必要がありますが、ジェネレーター関数の実行の一時停止と再開の特性により、より簡単な方法でツリー構造の走査を実現できます。この例では、キーワードが呼び出される
たびに、現在のノードの値が出力され、実行が一時停止され、メソッドの次の呼び出しを待ちます。ジェネレーター関数を使用する場合、キーワードを使用して子ノードを走査する必要があることに注意してください。キーワードはサブジェネレーター関数に制御を渡すことができ、サブジェネレーター関数がツリー構造全体を再帰的に走査できるようになります。yieldnext()
yield*yield*

次のメソッドでも、同じキー値を持つツリー配列をフラット化できます (Vue2 に基づく)
メソッド 1

    *flattenTreeGenerator(node, keys) {
    
    
      yield node;
      for (const key of keys) {
    
    
        if (Array.isArray(node[key])) {
    
    
          for (const child of node[key]) {
    
    
            yield* this.flattenTreeGenerator(child, keys);
          }
        }
      }
    },
    flattenTree(arr, keys) {
    
    
      const result = [];
      for (const node of arr) {
    
    
        for (const item of this.flattenTreeGenerator(node, keys)) {
    
    
          result.push(item);
        }
      }
      return result;
    },

注釈付きバージョン

/**
 * 生成器函数,用于扁平化树形结构
 * @param {Object} node - 当前节点
 * @param {Array} keys - 需要扁平化的属性列表
 */
function* flattenTreeGenerator(node, keys) {
    
    
  // 生成器函数,首先 yield 当前节点
  yield node;

  // 遍历需要扁平化的属性
  for (const key of keys) {
    
    
    // 如果当前节点有对应属性且值为数组,则递归遍历数组中的子节点
    if (Array.isArray(node[key])) {
    
    
      for (const child of node[key]) {
    
    
        yield* flattenTreeGenerator(child, keys);
      }
    }
  }
}

/**
 * 扁平化树形结构
 * @param {Array} arr - 原始树形结构
 * @param {Array} keys - 需要扁平化的属性列表
 * @returns {Array} - 扁平化后的结果
 */
function flattenTree(arr, keys) {
    
    
  // 初始化结果数组
  const result = [];

  // 遍历原始树形结构中的每个节点
  for (const node of arr) {
    
    
    // 使用生成器函数扁平化当前节点,并将结果 push 到结果数组中
    for (const item of flattenTreeGenerator(node, keys)) {
    
    
      result.push(item);
    }
  }

  // 返回扁平化后的结果数组
  return result;
}

移行

      let treeDataSource = [
        {
    
    
          id: "1",
          children: [
            {
    
    
              id: "11",
              name: "aa_sub1",
              desc: "这是一个描述_sub1",
              parentId: "1",
            },
          ],
        },
        {
    
    
          id: "2",
          children: [
            {
    
     id: "22", parentId: "2" },
            {
    
    
              id: "23",
              parentId: "2",
              childrens1: [
                {
    
    
                  id: "233",
                  parentId: "23",
                  childrenss2: [{
    
     id: "2333", parentId: "233" }],
                },
              ],
            },
          ],
        },
      ];
      const keys = ["children",'childrens1','childrenss2'];
      const flattened = this.flattenTree(treeDataSource, keys);

方法 2 – 通常の再帰 メソッド
とジェネレーターを再帰的に呼び出します。入力パラメーターは次のとおりです: 配列、キー値

    flattenTree(data, keys) {
    
    
      // 定义函数,传入两个参数,第一个是需要扁平化的树形数组,第二个是需要扁平化的关键字
      const result = []; // 定义一个空数组,用来存储扁平化后的结果
      function traverse(node) {
    
    
        // 定义一个名为 traverse 的函数,用于遍历节点
        result.push(node); // 将当前节点添加到结果数组中
        for (const key of keys) {
    
    
          // 遍历传入的关键字数组
          if (Array.isArray(node[key])) {
    
    
            // 如果当前节点的关键字对应的值是一个数组
            for (const child of node[key]) {
    
    
              // 遍历该数组中的子节点
              traverse(child); // 递归调用 traverse 函数,继续遍历子节点
            }
          }
        }
      }
      for (const node of data) {
    
    
        // 遍历原始树形数组中的每个节点
        traverse(node); // 对每个节点递归调用 traverse 函数,遍历所有子节点
      }
      return result; // 返回扁平化后的结果数组
    },

3. 特殊なケース 2 – 外側の層がオブジェクトである

     tree: {
    
    
        id: 1,
        name: "A",
        layer: [
          {
    
    
            id: 2,
            name: "B",
            children: [
              {
    
    
                id: 4,
                name: "D",
                items: [
                  {
    
    
                    id: 6,
                    name: "F",
                    categories: [
                      {
    
    
                        id: 8,
                        name: "H",
                      },
                    ],
                  },
                  {
    
    
                    id: 7,
                    name: "G",
                  },
                ],
              },
              {
    
    
                id: 5,
                name: "E",
              },
            ],
          },
          {
    
    
            id: 3,
            name: "C",
          },
        ],
      },
      flattenedTree: [],
  methods: {
    
    
  	// 方法1:生成器函数
    *flattenTreeGenerator(node, keys) {
    
    
      yield node;
      for (const key of keys) {
    
    
        if (Array.isArray(node[key])) {
    
    
          for (const child of node[key]) {
    
    
            yield* this.flattenTreeGenerator(child, keys);
          }
        }
      }
    },
    // 传入tree和每层数组的key名
    flattenTree() {
    
    
      this.flattenedTree = [
        ...this.flattenTreeGenerator(this.tree, ["layer", "children", "items"]),
      ];
      console.log(this.flattenedTree);
    },
   //-------------------------------------------
    //方法2:正常递归----flattenTree 方法是一个递归函数。
    flattenTree(node, keys) {
    
    
      const result = [node];
      keys.forEach((key) => {
    
    
        if (Array.isArray(node[key])) {
    
    
          result.push(
            ...node[key].flatMap((child) => this.flattenTree(child, keys))
          );
        }
      });
      return result;
    },
    const keys = ["layer", "childre", "items"]
    const flatArr = this.flattenTree(this.tree, keys);
  },

ツリー配列に復元する

    // 还原树形结构
    restoreTree(arr, idKey = "id", parentIdKey = "parentId") {
    
    
      const map = {
    
    };
      const roots = [];

      // 将节点按照 id 存储到一个对象中
      for (const node of arr) {
    
    
        map[node[idKey]] = {
    
     ...node, children: [] };
      }

      // 遍历每个节点,将其添加到其父节点的 children 属性中
      for (const node of arr) {
    
    
        const parent = map[node[parentIdKey]];
        if (parent) {
    
    
          parent.children.push(map[node[idKey]]);
        } else {
    
    
          roots.push(map[node[idKey]]);
        }
      }

      return roots;
    },

平坦化 —> 復元

<template>
  <div>
    <button @click="GeneratorFlat">生成器方法</button>
    <button @click="openAll">普通递归</button>
  </div>
</template>
<script>
export default {
    
    
  data() {
    
    
    return {
    
    };
  },
  methods: {
    
    
    // 生成器方法扁平化
    GeneratorFlat() {
    
    
      let treeDataSource = [
        {
    
    
          id: "1",
          children: [
            {
    
    
              id: "11",
              name: "aa_sub1",
              desc: "这是一个描述_sub1",
              parentId: "1",
            },
          ],
        },
        {
    
    
          id: "2",
          children: [
            {
    
     id: "22", parentId: "2" },
            {
    
    
              id: "23",
              parentId: "2",
              childrens1: [
                {
    
    
                  id: "233",
                  parentId: "23",
                  childrenss2: [{
    
     id: "2333", parentId: "233" }],
                },
              ],
            },
          ],
        },
      ];
      const keys = ["children", "childrens1", "childrenss2"];
      const flattened = this.flattenTree(treeDataSource, keys);
      console.log(flattened, "生成器递归结果=================");

      const restoredTree = this.restoreTree(flattened);
      console.log(restoredTree, "还原后的树=================");
    },
    // 普通递归
    openAll() {
    
    
      let treeDataSource = [
        {
    
    
          id: "1",
          children: [
            {
    
    
              id: "11",
              name: "aa_sub1",
              desc: "这是一个描述_sub1",
              parentId: "1",
            },
          ],
        },
        {
    
    
          id: "2",
          children: [
            {
    
     id: "22", parentId: "2" },
            {
    
    
              id: "23",
              parentId: "2",
              childrens1: [
                {
    
    
                  id: "233",
                  parentId: "23",
                  childrenss2: [{
    
     id: "2333", parentId: "233" }],
                },
              ],
            },
          ],
        },
      ];
      const keys = ["children", "childrens1", "childrenss2"];
      const flatteneds = this.flattenS(treeDataSource, keys);
      console.log(flatteneds, "普通递归结果=================");
    },
    //正常递归
    flattenS(data, keys) {
    
    
      // 定义函数,传入两个参数,第一个是需要扁平化的树形数组,第二个是需要扁平化的关键字
      const result = []; // 定义一个空数组,用来存储扁平化后的结果
      function traverse(node) {
    
    
        // 定义一个名为 traverse 的函数,用于遍历节点
        result.push(node); // 将当前节点添加到结果数组中
        for (const key of keys) {
    
    
          // 遍历传入的关键字数组
          if (Array.isArray(node[key])) {
    
    
            // 如果当前节点的关键字对应的值是一个数组
            for (const child of node[key]) {
    
    
              // 遍历该数组中的子节点
              traverse(child); // 递归调用 traverse 函数,继续遍历子节点
            }
          }
        }
      }
      for (const node of data) {
    
    
        // 遍历原始树形数组中的每个节点
        traverse(node); // 对每个节点递归调用 traverse 函数,遍历所有子节点
      }
      return result; // 返回扁平化后的结果数组
    },

    // ---------------------------------------------------------
    //生成器递归
    *flattenTreeGenerator(node, keys) {
    
    
      yield node;
      for (const key of keys) {
    
    
        if (Array.isArray(node[key])) {
    
    
          for (const child of node[key]) {
    
    
            yield* this.flattenTreeGenerator(child, keys);
          }
        }
      }
    },
    flattenTree(arr, keys) {
    
    
      const result = [];
      for (const node of arr) {
    
    
        for (const item of this.flattenTreeGenerator(node, keys)) {
    
    
          result.push(item);
        }
      }
      return result;
    },
// --------------------------------------------------------------------
    // 还原树形结构
    restoreTree(arr, idKey = "id", parentIdKey = "parentId") {
    
    
      const map = {
    
    };
      const roots = [];

      // 将节点按照 id 存储到一个对象中
      for (const node of arr) {
    
    
        map[node[idKey]] = {
    
     ...node, children: [] };
      }

      // 遍历每个节点,将其添加到其父节点的 children 属性中
      for (const node of arr) {
    
    
        const parent = map[node[parentIdKey]];
        if (parent) {
    
    
          parent.children.push(map[node[idKey]]);
        } else {
    
    
          roots.push(map[node[idKey]]);
        }
      }

      return roots;
    },
  },
};
</script>

おすすめ

転載: blog.csdn.net/weixin_43811753/article/details/129361256