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
}
从 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 则是
- 将某个 Group By Key 的物件下了栏位也取出(id, name),
- 被 groupBy 的部分放在 child 下
- 负责处理的 Fn,需要可以取得来源物件属性
Array<{
groupKey: string // groupBy 的 key (T)
groupData: Custom Object // groupBy 的 key Data (D)
child: Array<Custom Object> // 被 groupBy的資料 (C)
}>
最后方法长这样
第一个参数是需要被 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 Type
的GroupTreeby
方法,同时回传可判别型别
推断出型别
希望的结果
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…
你不可以做什么?
不能单纯使用变数去推断上下属性关系,意思就是你只能透过Function参数,传入A,然后让回传推论A
这里想要做的是,希望可以在 field 中,抓到 title 中的 key,约束避免 key 不一至的问题,没办法单纯定义一个 type 或是 interface 就直接可以推论。
无法识别出来
使用 ES6 宣告 Function 比较麻烦一点?
我们先看看使用一般方法的方式
function genericsTitleData<K extends TBodyDataFieldKey, I extends TBodyDataID>(title: TTitle<K>, data: IBodyData<K, I>[]): ITableTitleData<K, I> {
return {title, data};
}
很好,这可以,并且也能等于相对性
接着我们把 function 改成 es6 const function
透过 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)
转载自:https://juejin.cn/post/7283719464906293303