フロントエンド データの要件については、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()
メソッドを通じて続行できます。
ジェネレーター関数の実行が終了すると、出力値を調べるために使用されるイテレーター (反復子) が自動的に返されます。
ツリー構造を平坦化するプロセスでは、ツリーの各ノードを再帰的に走査する必要がありますが、ジェネレーター関数の実行の一時停止と再開の特性により、より簡単な方法でツリー構造の走査を実現できます。この例では、キーワードが呼び出される
たびに、現在のノードの値が出力され、実行が一時停止され、メソッドの次の呼び出しを待ちます。ジェネレーター関数を使用する場合、キーワードを使用して子ノードを走査する必要があることに注意してください。キーワードはサブジェネレーター関数に制御を渡すことができ、サブジェネレーター関数がツリー構造全体を再帰的に走査できるようになります。yield
next()
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>