如何对树数组进行递归?

How to do recursion for tree array?

我有一个无限深的数组树:

const folderData = [
  {
    folderId: '1',
    isSettingPermission: false,
    children: [
      {
        folderId: '2.1',
        isSettingPermission: false,
        children: [
          {
            folderId: '3.1',
            isSettingPermission: false,
            children: [],
          },
        ],
      },
      {
        folderId: '2.2',
        isSettingPermission: false,
        children: [],
      },
    ],
  },
];

我想要做的是传入一个 folderId 并将文件夹及其子文件夹的“isSettingPermission”从 false 更改为 true。

const changeChildrenPermission = (folder) =>
  folder.map(({ folderId, children }) => ({
    folderId,
    isSettingPermission: true,
    children: changeChildrenPermission(children),
  }));

export const findFolder = (
  folders,
  id
) => {
  let res = null;
  if (folders.folderId === id) {
    res = changeChildrenPermission([folders]);
  }

  for (let i = 0; i < folders.children.length; i++) {
    findFolder(folders.children[i], folders.children[i].folderId);
  }
  return res;
};

这就是我卡住的地方,当我传入根文件夹 ID 时,它会返回正确的树状数组。但是当我在树的下方传递一个 folderId 时,结果将给我 null。

const result = findFolder(folderData, '1');
const result = findFolder(folderData, '2.1'); // this returns null

您的代码存在多个问题:

  • 您没有 childCount 道具
  • folders.folderId 将永远是 undefined 并且永远不会等于 id,因为数组没有 folderId。只有它的成员有这个 属性.

这是您要实现的目标的工作版本。注意你的代码改变了数组(这可能是你想要它做的)。但这意味着您需要 运行 对初始数据的新副本进行每次测试,否则您将更改初始数据,并且第二个测试将从第一个测试已经变异的数据开始。
为了解决这个问题,我写了一个基本的 clone 函数。

const folderData = [
  {
    folderId: '1',
    isSettingPermission: false,
    children: [
      {
        folderId: '2.1',
        isSettingPermission: false,
        children: [
          {
            folderId: '3.1',
            isSettingPermission: false,
            children: []
          },
        ],
      },
      {
        folderId: '2.2',
        isSettingPermission: false,
        children: []
      },
    ],
  },
];

const changeChildrenPermission = ({ folderId, children }) => ({
  folderId,
  isSettingPermission: true,
  children: children?.map(child => changeChildrenPermission(child))
})

const findFolder = (folders, id) => {
  if (folders?.length) {
    const index = folders.findIndex(({ folderId }) => folderId === id);
    if (index > -1) {
      // if we found the folder
      folders[index] = changeChildrenPermission(folders[index]);
    } else {
      // if folder was not found, check children
      folders.forEach(({ children }) => {
        findFolder(children, id);
      })
    }
  }
};

const clone = data => JSON.parse(JSON.stringify(data));

let data;
data = clone(folderData);
findFolder(data, '1');
console.log(data);

data = clone(folderData);
findFolder(data, '2.1');
console.log(data);

我认为最干净的方法是稍微改变 changeChildrenPermission 以便它在单个节点上工作并递归地在其所有子节点上工作,而不是接受节点数组的当前版本。

然后我们的主函数可以映射我们的元素列表,如果我们在匹配的 id 上,调用 changeChildrenPermission ,否则递归更新子元素。它可能看起来像这样:

const changeChildrenPermission = ({folderId, children}) => ({
  folderId,
  isSettingPermission: true,
  children: children .map (changeChildrenPermission)
})

const updateFolder = (id) => (xs) => 
  xs .map (x => x .folderId == id 
    ? changeChildrenPermission (x)
    : {... x, children: updateFolder (id) (x.children)}
  )

const folderData = [{folderId: '1', isSettingPermission: false, children: [{folderId: '2.1', isSettingPermission: false, children: [{folderId: '3.1', isSettingPermission: false, children: []}]}, {folderId: '2.2', isSettingPermission: false, children: []}]}];

console .log (updateFolder ('2.1') (folderData))
.as-console-wrapper {max-height: 100% !important; top: 0}

但是,如果现有的 changeChildrenPermission 对您有用,因为它有其他用途,那么更改 updateFolder 来使用它并不难。在调用函数之前将找到的节点包装在一个单元素数组中,并在 return:

之后解包

const changeChildrenPermission = (folder) =>
  folder .map (({folderId, children}) => ({
    folderId,
    isSettingPermission: true,
    children: changeChildrenPermission (children),
  }));

const updateFolder = (id) => (xs) => 
  xs .map (x => x .folderId == id 
    ? changeChildrenPermission ([x]) [0]
    : {... x, children: updateFolder (id) (x.children)}
  )

const folderData = [{folderId: '1', isSettingPermission: false, children: [{folderId: '2.1', isSettingPermission: false, children: [{folderId: '3.1', isSettingPermission: false, children: []}]}, {folderId: '2.2', isSettingPermission: false, children: []}]}];

console .log (updateFolder ('2.1') (folderData))
.as-console-wrapper {max-height: 100% !important; top: 0}

注意两个版本之间的小变化:

-    ? changeChildrenPermission (x)
+    ? changeChildrenPermission ([x]) [0]

但我们可能希望能够以更通用的方式重用 changeChildrenPermission 背后的想法。这是一个抽象出要更改的字段名称和要保存的新值的版本:

const updateNestedField = (name, value) => (node) => ({
  ... node,
  [name]: value,
  children: node .children .map (updateNestedField (name, value))
})

const updateFolder = (id) => (xs) => 
  xs .map (x => x .folderId == id 
    ? updateNestedField ('isSettingPermission', true) (x)
    : {... x, children: updateFolder (id) (x.children)}
  )

const folderData = [{folderId: '1', isSettingPermission: false, children: [{folderId: '2.1', isSettingPermission: false, children: [{folderId: '3.1', isSettingPermission: false, children: []}]}, {folderId: '2.2', isSettingPermission: false, children: []}]}];

console .log (updateFolder ('2.1') (folderData))
.as-console-wrapper {max-height: 100% !important; top: 0}

最后,我们可以对主函数进行类似的抽象,传入一个任意谓词以确定要更改的节点和一个转换函数来转换这些节点:

const updateNestedField = (name, value) => (node) => ({
  ... node,
  [name]: value,
  children: node .children .map (updateNestedField (name, value))
})

const updateList = (pred, transform) => (xs) =>
  xs .map (x => pred (x) ? transform (x) : {... x, children: updateList (pred, transform) (x .children)})

const updateFolder = (id) => updateList (
  x => x .folderId == id,
  updateNestedField ('isSettingPermission', true)
)

const folderData = [{folderId: '1', isSettingPermission: false, children: [{folderId: '2.1', isSettingPermission: false, children: [{folderId: '3.1', isSettingPermission: false, children: []}]}, {folderId: '2.2', isSettingPermission: false, children: []}]}];

console .log (updateFolder ('2.1') (folderData))
.as-console-wrapper {max-height: 100% !important; top: 0}