Simba
Simba
TYPESCRIPT

Typescript针对树这一数据结构声明类型的思考

Typescript针对树这一数据结构声明类型的思考
3 min read
#Typescript

场景

后端接口数据:

[
  {
    label: 'xxx',
    value: 1,
    // 第一层一定包含children字段
    children: [
      {
        label: 'xxx',
        value: 1,
        // 第二层也一定包含children字段
        children: [
          {
            label: 'xxx',
            value: 1,
            // 第三层不一定包含children字段
            children: [],
          },
        ],
      },
    ],
  },
];

简单来说就是一个树的嵌套中 前两层与第三层的数据结构不固定但相似度极高  这时候是采用复用的方式 or 冗余一层特殊的结构来进行声明呢 让我陷入了沉思🤔

前端逻辑:

/*伪代码*/
 
interface ItemBase {
  label: string
  value: number
  children?: ItemBase[] // 因为要兼容第三层的Item数据 所以这里children变成可选的
}
 
// select函数其实永远接收的是前面两层item的数据
function select({ children }: ItemBase) {
  return children.join()
}
 
// 模拟后端请求
const data = get<ItemBase[]>() // <- data: ItemBase[]
 
// 因为这里children有可能是undefined 与select函数参数类型不符
// 即在定义的过程中为了复用相同结构 兼容了children为可选的情况
select(data[0].children) // <- ts type error ❌

改造方案

  1. 优雅的硬写 📓 (extending-types)
/*添加为什么要定义这么套娃娃中娃的注释*/
 
interface ItemBase {
  label: string
  value: number
}
 
interface Item extends ItemBase {
  children: ItemSecond[]
}
 
interface ItemSecond extends ItemBase{
  children: ItemThird[]
}
 
interface ItemThird extends ItemBase {
  children?: Item[]
}
 
const data = get<Item[]>()
 
select(data[0].children) // ✅
  1. 优雅还带点可读性的复用 📒 (non-null-assertion-operator)
interface ItemBase {
  label: string
  value: number
  children?: ItemBase[]
}
 
const data = get<ItemBase[]>()
 
// 通过断言的方式来特殊处理当前的业务逻辑情况
// 添加关于当前接口结构要这么处理的注释是极好的
select(data[0].children!) // ✅
  1. 优雅的跪下来求后端大佬不要传空或者不传children字段回来 🧎‍♂️

说在最后

综上所述都是可以稍微优雅地解决这样特殊树结构的Typescript定义方式 相信你看完以后也有自己心里满意的答案了 适合自己的就是最好的嗷

End 🎉

(当然我最后选的是第二种解决方式 业务相关的我喜欢写注释在业务代码里 逃 🏃