TypeScript实现arrayToTree如何无需@ts-ignore解决类型报错
无@ts-ignore的最终实现
// 定义带kids属性的树节点类型,子节点也保持相同结构 type WithKids<T> = T & { kids: WithKids<T>[] } // 声明三个泛型: // T: 原始数组项的类型,约束其itemIdKey、parentIdKey对应的值为 字符串/数字/空值 // ID: itemIdKey的字面量类型 // PID: parentIdKey的字面量类型 const arrayToTree = < T extends Record<ID | PID, string | number | null | undefined>, ID extends string, PID extends string >( array: T[], { itemIdKey, parentIdKey }: { itemIdKey: ID; parentIdKey: PID } ): WithKids<T>[] => { if (itemIdKey === parentIdKey) { throw new Error('itemIdKey and parentIdKey has to be different') } const map: Record<string | number, WithKids<T>> = {} const roots: WithKids<T>[] = [] // 第一次遍历生成所有带kids属性的节点存到map array.forEach(item => { const id = item[itemIdKey] // 运行时校验id合法性,避免非基础类型作为索引 if (typeof id !== 'string' && typeof id !== 'number') { throw new Error(`itemId value must be string or number, got ${typeof id}`) } map[id] = { ...item, kids: [] } }) // 第二次遍历挂载节点 array.forEach(item => { const parentId = item[parentIdKey] const currentNode = map[item[itemIdKey] as string | number] // 父id为空、为'0'、或者父id不存在于map时,都作为根节点 if (!parentId || parentId === '0' || !map[parentId]) { roots.push(currentNode) } else { map[parentId].kids.push(currentNode) } }) return roots }
核心改动说明
- 新增泛型约束:明确原始数组项
T的itemIdKey、parentIdKey对应的属性值只能是字符串/数字/空值,TS就可以确认这两个值可以作为对象索引,解决索引类型不兼容的报错 - 拆分泛型为
T、ID、PID三个,捕获key的具体字面量类型,TS可以自动窄化对应属性的类型,解决和字符串'0'比较的类型报错 - 修正原逻辑缺陷:原来挂载子节点时直接push原始数组项,改为push map中已生成的带kids属性的节点,支持多层嵌套
- 新增运行时校验:避免传入非字符串/数字类型的id导致运行异常
- 新增父节点不存在的兼容处理:如果父id在map中找不到对应节点,直接作为根节点,避免运行时报错
内容的提问来源于stack exchange,提问作者Ni Amon
相关产品推荐
相关产品推荐

