【发布时间】:2021-07-06 19:11:49
【问题描述】:
说我有这个功能
async function getTracksOrAlbums(
tracksOrAlbums: {tracks: Track[]} | {albums: Album[]},
notFound: NotFound,
): Promise<GetTracksOrAlbums> {
if ('albums' in tracksOrAlbums) {
const albumsResponse = await getManyAlbums(tracksOrAlbums.albums)
const result = albumsResponse.filter((item, i): item is AlbumResponse => {
return filterErrors(() => {
notFound.total++
notFound.data.push(tracksOrAlbums.albums[i])
}, item)
})
return {
data: result,
report: {found: result.length, notFound},
}
}
const tracksResponse = await getManyTracks(tracksOrAlbums.tracks)
const result = tracksResponse.filter((item, i): item is TrackResponse => {
return filterErrors(() => {
notFound.total++
notFound.data.push(tracksOrAlbums.tracks[i])
}, item)
})
return {
data: result,
report: {found: result.length, notFound},
}
}
我想将以下部分抽象成一个单独的函数:
const albumsResponse = await getManyAlbums(tracksOrAlbums.albums)
const result = albumsResponse.filter((item, i): item is AlbumResponse => {
return filterErrors(() => {
notFound.total++
notFound.data.push(tracksOrAlbums.albums[i])
}, item)
})
在几乎伪代码中,我想做以下几行:
async function filterTracksOrAlbums<T extends Track | Album>(
tracksOrAlbums: Array<T>,
notFound: NotFound,
) {
const searchRes = await getManyTracks(tracksOrAlbums) // or await getManyAlbums(tracksOrAlbums)
const result = searchRes.filter(
// I don't really know how to define here a generic type guard
(item, i): item is T => {
//
return filterErrors(() => {
notFound.total++
notFound.data.push(tracksOrAlbums[i])
}, item)
},
)
return result
}
我还想表达输入之间的关系:
(Track | Album)[]
和输出:
(TrackResponse | AlbumResponse)[]
不会导致重复,因为如果我要为 playlists 或 user data 添加另一个案例,
每次filter 回调时我都必须重写它是对应的 type
警卫来匹配一个可能的PlaylistResponse和UserResponse
有没有办法封装这段代码而不会导致重复?我开始使用 函数重载,但使代码不必要地复杂。
getManyAlbums 和 getManyTracks 是对 API 执行请求的函数,可以模拟为:
function getManyAlbums(
albums: Album[],
): Promise<(AlbumResponse | ErrorResponse)[]> {
return Promise.resolve([
{ok: false, message: 'error message'},
{ok: true, data: {id: '1', name: 'bar', artist: 'foo'}},
])
}
function getManyTracks(
tracks: Track[],
): Promise<(TrackResponse | ErrorResponse)[]> {
return Promise.resolve([
{ok: true, data: {id: '1', name: 'bar', artist: 'foo', isrc: 'baz'}},
{ok: false, message: 'error message'},
])
}
这些是相关的类型声明:
type Track = {artist: string; song: string}
type Album = {artist: string; album: string}
type TrackResponse = {
ok: true
data: {id: string; name: string; artist: string; isrc: string}
}
type AlbumResponse = {
ok: true
data: {id: string; name: string; artist: string}
}
type ErrorResponse = {ok: false; message: string}
type NotFound = {total: number; data: unknown[]}
type GetReturnType<T> = {
data: T
report: {found: number; notFound: NotFound}
}
type GetTracks = GetReturnType<TrackResponse[]>
type GetAlbums = GetReturnType<AlbumResponse[]>
type GetTracksOrAlbums = GetTracks | GetAlbums
【问题讨论】:
-
请考虑修改这个问题中的代码以构成一个minimal reproducible example,当它放入像The TypeScript Playground (link)这样的独立IDE时,清楚地展示了您面临的问题(所以没有未声明或未定义类型或值)。这将使那些想要帮助您的人立即着手解决问题,而无需首先重新创建它。它将使您得到的任何答案都可以针对定义明确的用例进行测试。
-
我试图简化它并添加运行它所需的代码,但我认为它很长。我希望这次会更清楚。
-
是的,那里发生了很多事情;您能否尝试将其削减,以便在不包括所有功能的情况下仍然显示重复问题?除非您能提出真正的 generic 抽象,否则您最终将需要进行大量额外的类型操作(重载或断言或辅助函数)才能使其工作,这可能是真的。
-
因此,您可以执行类似this 的操作,其中您的联合实际上是具有判别属性的可区分联合(本例中为
type),然后编写对输入/输出进行操作的通用函数类型地图。但我不知道这对你是否值得。如果是这样,我也许可以写一个答案,但我肯定更愿意在一个更简单的例子中这样做。祝你好运! -
好的,我尝试了你的建议,我很喜欢!我最终得到了我在此处标记的代码:Playground 在意识到没有必要之后,我也摆脱了函数重载。但由于我对 TypeScript 比较陌生,所以我不完全确定
标签: typescript overloading dry