一般我们使用泛型时,是为了表达某些字段的类型是动态的,或者表达这个封装是适用于多种类型场景的。
假如我们有如下泛型类型:
type Result<T> = { data: T}
易知,该类型的 data
字段是动态的。
如果我们还有如下 2 个非泛型类型:
type Foo = { name: string}type Bar = { email: string}
那么,对于 Result<Foo>
和 Result<Bar>
我们可以这样使用:
const fooResult: Result<Foo> = { data: { name: 'tom' }}const barResult: Result<Bar> = { data: { email: 'jerry@examole.com' }}// foo's type is Fooconst foo = fooResult.data;// bar's type is Barconst bar = barResult.data;
你可能要说了,当然是这样,有什么可讨论的!
我们变换一种姿势来表达上面的泛型类型,看看如何实现。
我们希望,当我们使用 Result<Foo>
时(后面用 Result2<Foo>
代替,以区别上面的声明方式),调用方式是这样的:
const fooResult: Result2<Foo> = { foo: { name: 'tom' }}// foo's type is Fooconst foo = fooResult.foo;
同样,Result2<Bar>
是这样的:
const barResult: Result2<Bar> = { bar: { email: 'jerry@examole.com' }}// bar's type is Barconst bar = fooResult.bar;
发现 Result
和 Result2
的类型差异了没?
对于 Result
,其字段的类型是动态的,但字段名是固定的(data
);而对于 Result2
其字段类型和字段名都是动态的。
Result2
甚至支持这样的用法(而用 Result
就很难这样操作了):
const mixedResult: Result2<Foo | Bar> = ...;// 分别访问各自字段mixedResult.foo.name;mixedResult.bar.email;
那么,怎么编写像 Result2
这样的泛型类型?
让字段名和字段类型都动态化的泛型技巧
其实,你看过也就明白了:
type Result2<T> = { foo: T extends Foo ? Foo : never; bar: T extends Bar ? Bar : never;}
是不是恍然大悟?
从中也能看到它的一个限制:定义 Result2<T>
类型时,我们需要事先知道自己支持哪些类型,即 T
是已知的。
那它的使用场景是什么?
我能想到的一个场景是:需要对外提供一套 API,API 返回的类型都是已知的,而且有些 API 的返回值是对其它 API 的返回值进行组合返回 (如,有的返回 {foo}
、有的 {bar}
,有的则是 {foo,bar}
)。本文介绍的技巧便能派上有场。
补充一下实际使用的方式:
// 只有 foo 时const fooResult2: Result2<Foo> = { foo: { name: 'tom' }, // 注意这里 bar: undefined as never,}// type Fooconst foo2 = fooResult2.foo;// type neverconst bar2 = fooResult2.bar;// 2个都有时const mixedResult: Result2<Foo | Bar> = { foo: { name: 'tom' }, bar: { email: 'jerry@examole.com'
},
}
// type Foo
mixedResult.foo;
// type Bar
mixedResult.bar;
介绍完了,分享下你的看法吧?
文章转载自背井,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。