类型推导
目前 TS 的类型推导,仍不算完美。假定我们想做一个类似 useFetch / useLoader 这样的封装,思维的习惯会聚焦在 Query / Result 这两个类型上(就是 Input/Ouput),但围绕这两个类型做函数参数或 Object 类型,来实现的类型推导体系(即在实际调用层,通过实际的参数,来取代泛型的声明),目前在 TS 上是有一些问题的(不是做不到,是很绕也很繁琐)。
在 TS 里,更有效的做法,是关注载体(而不是关注端点)。比如:https://gitee.com/janpoem/use-the-loader/blob/master/src/useTheLoader.ts
即:type Loader<Q, R> = (Q) => Promise<R>
。Loader 自身就负载了 Query / Result,然后通过 infer
即可提取出 Loader 的 Q/R。
这里又有另外一个问题,即 Parameters
这个在类型推导中使用的局限性。他无法有效的展开数组元素的类型推导。使用这个会让你需要写很多的 ts-ignore 和 eslint-disable-next-line。
回归 interface —— 泛型声明过多
假定我们做一个数据视图,这个视图包含两种以上截然不同数据结构。我们首先会习惯用泛型和类型推导做,这样能做到,但存在的问题是,关联的组件、函数、Class,都要一个一个套上对应的泛型声明,非常麻烦,维护难度高。
这时候,可以回归到 interface
的方式上,即:
intreface BasicSheetData {
type: string;
// 基础结构体
}
interface ASheetData extends BasicSheetData {
type: 'a',
// ASheet 结构体
}
interface BSheetData extends BasicSheetData {
type: 'b',
// BSheet 结构体
}
// 通过函数将类型推到成某个类型
const isASheetData = (d: BasicSheetData): d is ASheetData => d.type === 'a';
const isBSheetData = (d: BasicSheetData): d is BSheetData => d.type === 'b';
function usage(data: BasicSheetData): void {
if (isASheetData(data)) {
// 这 scope 里,data 被推导成 ASheetData
// do something for ASheetData
} else if (isBSheetData(data)) {
// 这 scope 里,data 被推导成 BSheetData
// do something for BSheetData
}
}
越到底层,越只需要关注 BasicSheetData
。具体到具体类型,关注具体类型。公共组件,则通过 isXX
方法,来将 BasicSheetData
推导成特定类型即可。
这样就能大大简化了你的类型声明(尤其批评国外现在各种 npm 库,那个泛型满天飞,比如 react-hook-form,本质上用不到那些泛型)。
到底是 type 还是 interface,是 ,
还是 ;
回到 TS,我才发现我对 C 语言是有多偏执,我是对 type (struct 声明方式)真的是情有独钟。
但在使用过程还是应该要注意,interface
能准确表达继承的涵义,尤以 TS 这种泛型的声明模式 (基于 extends
,不支持 Scala 那种),如果在类型设计存在继承关系的,还是应该要用 interface
。
type T = P & { }
并不算做准确的类型继承,这里 T 和 P 是两种类型,T 具有 P 部分属性,彼此无关联性(尤其是,interface 允许 override,type 的 &
会让一个属性具备两种类型检查,你必须明确声明 Omit<P, 'prop'> & {}
)。
但,interface 和 type 之间,可以互相串,比如:
type T = {}
interface A extends T {}
type B = A & {}
其次是,type 和 interface 里的字段声明,到底用 ,
还是 ;
,我是习惯用 ,
,主要是这样的结构易于被复制、粘贴,或者是将某个 JS/JSON 结构,改写为类型声明,这样快。其实无区别,TS 最终都要转译,最终转译产物是 ;
。
两者之间也可以共用共存,没毛病,不必计较。