1

场景1:从树中查找查找符合条件的节点(一个)

const findNodeById = (nodes, id) => {
  // 遍历当前层的所有数组元素
  for (const node of nodes) {
    // 找到目标节点,直接返回,递归结束
    if (node.id === id) {
      // 这里会有两种情况:
      // 1. 如果这里不是在递归中,node会作为调用处的返回值,函数结束
      // 2. 如果这里是在递归中,node会作为递归调用处的返回值(赋值给foundNode的地方),当前层的递归结束
      return node;
    }

    // 未找到目标节点,如果存在children,则将其作为nodes和原始id一起递归调用findNodeById
    if (node.children) {
      const foundNode = findNodeById(node.children, id);

      // 找到目标节点,直接返回,递归结束
      if (foundNode) {
        return foundNode;
      }
    }
  }

  // 未目标节点,递归结束
  return null;
};

const tree = [
  {
    id: '1',
    name: '营销体系',
    children: [
      {
        id: '1-1',
        name: '营销体系-南区开发团队',
      },
    ],
  },
  {
    id: '2',
    name: '交付体系',
  },
];

const res = findNodeById(tree, '1-1');
console.log(res);
//  {"id":"1-1","name":"营销体系-南区开发团队"}

场景2:从树中查找符合条件的节点(所有)

const getNodes = (nodes) => {
  const nextNodes = [];

  for (const node of nodes) {
    const { children, ...rest } = node;

    if (rest.value > 1) {
      nextNodes.push(rest);
    }

    if (children) {
      // 关键代码,将返回值赋值给children
      nextNodes.push(...getNodes(children));
    }
  }

  // 如果只有一级,这里返回的就是返回给外部调用处
  // 如果有多级,这里会返回给nextNodes.push
  return nextNodes;
};

const tree = [
  {
    id: '1',
    name: '营销体系',
    value: 1,
    children: [
      {
        id: '1-1',
        name: '营销体系-南区开发团队',
        value: 2,
      },
    ],
  },
  {
    id: '2',
    name: '交付体系',
    value: 2,
  },
];

const res = getNodes(tree);
console.log(res);
// [{"id":"1-1","name":"营销体系-南区开发团队","value":2},{"id":"2","name":"交付体系","value":2}]

优化一下,将识别目标的判断提取为函数依赖,提高灵活性

const getNodes = (nodes, checkIsTarget) => {
  const nextNodes = [];

  for (const node of nodes) {
    const { children, ...rest } = node;

    if (checkIsTarget(rest)) {
      nextNodes.push(rest);
    }

    if (children) {
      // 关键代码,将返回值赋值给children
      nextNodes.push(...getNodes(children, checkIsTarget));
    }
  }

  // 如果只有一级,这里返回的就是返回给外部调用处
  // 如果有多级,这里会返回给nextNodes.push
  return nextNodes;
};

const tree = [
  {
    id: '1',
    name: '营销体系',
    value: 1,
    children: [
      {
        id: '1-1',
        name: '营销体系-南区开发团队',
        value: 2,
      },
    ],
  },
  {
    id: '2',
    name: '交付体系',
    value: 2,
  },
];

const res = getNodes(tree, (node) => node.value > 1);
console.log(res);
// [{"id":"1-1","name":"营销体系-南区开发团队","value":2},{"id":"2","name":"交付体系","value":2}]

场景3:修改树中的指定节点名

修改节点名和修改节点值都可以用这种方式。只不过该方法会修改原始的树,简单的处理方式是在传入前提供tree的副本。(后面会添加不修改原始tree的版本。)

const updateTreeName = (nodes, currKey, targetKey) => {
  for (const node of nodes) {
    // 检查属性是否存在,如果存在则添加一个属性,并且把原始属性的值赋值给新属性
    if (node[currKey]) {
      const value = node[currKey];
      node[targetKey] = value;
      delete node[currKey];
    }

    if (node.children) {
      updateTreeName(node.children, currKey, targetKey);
    }
  }
};

const tree = [
  {
    id: '1',
    name: '营销体系',
    children: [
      {
        id: '1-1',
        name: '营销体系-南区开发团队',
      },
    ],
  },
  {
    id: '2',
    name: '交付体系',
  },
];

const res = updateTreeName(tree, 'name', 'title');
console.log(res);
// [{"id":"1","title":"营销体系","children":[{"id":"1-1","title":"营销体系-南区开发团队"}]},{"id":"2","title":"交付体系"}]

场景4:修改树中的指定节点名(不修改原树)

const updateTreeName = (nodes, currKey, targetKey) => {
  const nextNodes = [];

  for (const node of nodes) {
    const { children, [currKey]: value, ...rest } = node;

    // 得到原始key的值
    if (currKey in node) {
      rest[targetKey] = value;
    }

    if (children) {
      // 关键代码,将返回值赋值给children
      rest.children = updateTreeName(children, currKey, targetKey);
    }

    nextNodes.push(rest);
  }

  // 如果只有一级,这里返回的就是返回给外部调用处
  // 如果有多级,这里返回给children
  return nextNodes;
};

const tree = [
  {
    id: '1',
    name: '营销体系',
    children: [
      {
        id: '1-1',
        name: '营销体系-南区开发团队',
      },
    ],
  },
  {
    id: '2',
    name: '交付体系',
  },
];

const res = updateTreeName(tree, 'name', 'title');
console.log(res);
// [{"id":"1","title":"营销体系","children":[{"id":"1-1","title":"营销体系-南区开发团队"}]},{"id":"2","title":"交付体系"}]

总结

递归模式分类:

模式1:不需要返回值递归
这类的递归很简单,只需要记得添加一个递归调用的条件,然后调用就行。

const recursion = (nodes) => {
  for (const node of nodes) {
    // do whatever you want

    // 添加递归的调用条件,然后调用就好
    if (node.children) {
      recursion(node.children);
    }
  }
};

下面给出一个示例,用来修改树上的节点:

const func = (nodes, updater) => {
  for (const node of nodes) {
    node = updater(node);
    if (node.children) {
      // 要素1:递归调用
      func(node.children, updater);
    }
  }
};

const tree = [
  {
    id: '1',
    name: '营销体系',
    value: 1,
    children: [
      {
        id: '1-1',
        name: '营销体系-南区开发团队',
        value: 2,
      },
    ],
  },
  {
    id: '2',
    name: '交付体系',
    value: 2,
  },
];

func(tree, (node) => {
  const { name: title, ...rest } = node;

  // 将name字段更名为title
  return {
    ...rest,
    title,
  };
});

模式2. 获取树中符合条件的节点
这个类型的递归,相对于上面的,需要注意的地方就多了一点。

  1. 创建一个容器来收集目标节点
  2. 判断是否找到目标节点,并且放到容器中
  3. 返回目标容器
  4. 如果发生了递归,则将递归的结果也塞到容器中
const getNodes = (nodes) => {
  // (1/4)
  const nextNodes = [];
  for (const node of nodes) {

    // (2/4)
    if (node.id === '1') {
      nextNodes.push(node)
    }

    // (4/4)
    if (node.chidren) {
      nextNodes.push(...getNodes(item.children))
    }
    
  }

  // (3/4)
  return nextNodes;
};

下面给出一个示例,用于从树中递归获取目标节点集合:

const getNodes = (nodes, isTarget) => {
  const nextNodes = [];

  for (const node of nodes) {
    if (isTarget(node)) {
      nextNodes.push(node);
    }

    if (node.children) {
      nextNodes.push(...getNodes(node.children, isTarget));
    }
  }

  return nextNodes;
};

const tree = [
  {
    id: '1',
    name: '营销体系',
    value: 1,
    children: [
      {
        id: '1-1',
        name: '营销体系-南区开发团队',
        value: 2,
      },
    ],
  },
  {
    id: '2',
    name: '交付体系',
    value: 2,
  },
];

getNodes(tree, (node) => {
  // 识别目标
  return node.value === 2;
});

热饭班长
3.7k 声望434 粉丝

先去做,做出一坨狗屎,再改进。