likes
comments
collection
share

Typescript泛型的无罪推定

作者站长头像
站长
· 阅读数 14

意指变数若未被证实及判决有罪,在审判上应推定为无罪

Typescript泛型的无罪推定

什么是泛型(Generic Type)?

是指在定义函式、介面或类别的时候,不预先指定具体的型别,而在使用的时候再指定型别的一种特性。

简单的说,泛型是 Typescript 的 Type Function,

可以做什么?

可以帮我们做型别推定,进去是什么,出来是什么

简单一点的例子

Select Option,有的时候我们的 value 会是 number,如果限制只能是 string ,那参数传入时需要先转换过,参数传出后也需要转换(假设API 来源与表单送出栏位都是 number),所以我们透过 Generic Type 那就可以解决这样的问题。


interface TOption<T> {
    value: T;
    text: string;
}

interface ISelect2Props<T> extends FCProps {
    value?: T
    options?: TOption<T>[]
    onChange?: (value: T) => void
}

const Select2 = <T extends string|number>({
    value,
    options,
    onChange,
}: ISelect2Props<T>) => {
    //...ignrore
}

Typescript泛型的无罪推定

从 1 ~ 6 回推,6就是宣告变数的概念,并且必须是 string 或 number 其中一个

可以看到我们在 onChange 的时候,typescript 已经能够推论 value 是 number

但如果我们直接设定 value type 是 string|number 的话,那onChange 则会直接就是 string|number,你要接收的那一端只会接收其中一种,就会型别错误。

复杂一点的例子

const res = rows.map(project => {
  const teamStages = project.stage?.reduce((curr: ProjectsWithGantt['children'], stage) => {
      let rowIndex = curr.findIndex(team => team.id === stage.team.id);
      const childTask = {
          id: stage.id,
          text: stage.title,
          dataLevel: EDataLevel.task,
          sequence: stage.sequence,
          links: stage.toStages?.map(row => row.toId),
      };


      if(rowIndex === -1){
          // Team
          return [...curr, {
              id: stage.team.id,
              text: stage.team.name,
              dataLevel: EDataLevel.team,
              barColor: stage.team.theme?.color,
              children: [childTask],
          }];
      }

      // Task
      curr[rowIndex].children.push(childTask);


      return curr;
  }, [])

  // Project
  return {
      id: project.id,
      text: project.name,
      dataLevel: EDataLevel.project,
      children: teamStages,
  }
})

原始将阵列物件Group写法,没有型别问题,因为没有通过一个方法包装过

需求是,我们希望把复杂的GroupBy处理,新增一个方法来包装简化使用,并且做到通用化,一般的 GroupBy 是将某个 属性当作新的 Key,而这个Group By 则是

  1. 将某个 Group By Key 的物件下了栏位也取出(id, name),
  2. 被 groupBy 的部分放在 child 下
  3. 负责处理的 Fn,需要可以取得来源物件属性
Array<{
    groupKey: string // groupBy 的 key (T)
    groupData: Custom Object // groupBy 的 key Data (D)
    child: Array<Custom Object> // 被 groupBy的資料 (C)
}>

最后方法长这样

Typescript泛型的无罪推定

第一个参数是需要被 groupBy 的阵列物件 第二个参数是要如何 groupBy 的方法 C 则是代表 child D 则是代表 groupData T 则是代表 传入 阵列物件中 的 物件

以C为范例来看,从 1 ~ 5 的回推,5则是宣告变数的概念。 3~4 的部分,就是我们把 参数从2拿到的型别,放到4的回传位置

如果你把它想成从 5 ~ 1,你就会觉得 C 需要自己带入型别,但… 麻烦了,工具是要帮助我们,不是增加繁琐工作

groupTreeBy<{
   id: string,
   repo: string,
   title: string,
   // .....ignore
}>(xxx, ()=> ...)

使用Generic TypeGroupTreeby 方法,同时回传可判别型别

Typescript泛型的无罪推定

推断出型别

Typescript泛型的无罪推定

希望的结果

bear-jsutils/src/array/array.spec.ts at main · imagine10255/bear-jsutils

Common tools and methods for project development. Contribute to imagine10255/bear-jsutils development by creating an…

github.com

你不可以做什么?

不能单纯使用变数去推断上下属性关系,意思就是你只能透过Function参数,传入A,然后让回传推论A

example

这里想要做的是,希望可以在 field 中,抓到 title 中的 key,约束避免 key 不一至的问题,没办法单纯定义一个 type 或是 interface 就直接可以推论。

Typescript泛型的无罪推定

无法识别出来

使用 ES6 宣告 Function 比较麻烦一点?

我们先看看使用一般方法的方式

function genericsTitleData<K extends TBodyDataFieldKey, I extends TBodyDataID>(title: TTitle<K>, data: IBodyData<K, I>[]): ITableTitleData<K, I> {
    return {title, data};
}

很好,这可以,并且也能等于相对性

Typescript泛型的无罪推定

接着我们把 function 改成 es6 const function

Typescript泛型的无罪推定

透过 webstorm 直接转

const tableTitleData = <K extends string, I extends TBodyDataID>(title: TTitle<K>, data: IBodyData<I, K>[]): ITableTitleData<K, I> => ({
    title,
    data,
});

恩,一样是OK的,也没多写什么,只不过格式有一点不一样

结论

使用 Generic Type 可以帮助我们更安全的产出代码,也更安全的型别保护。但也因为较为思考模式上较为复杂,所以还是需要审视自己实际的状况使用。

当然如果本来就觉得 TS绑手绑脚的人,可以直接略过,因为这需要一些时间去领悟(我的经验是从 JS 到 flowType 再到 Typescirpt)

medium.com/@imaginechi…