Last active
August 21, 2019 17:10
-
-
Save danfma/9e6ca5903ebe0d6e77dd62395ecd9050 to your computer and use it in GitHub Desktop.
Problem with TypeScript conditional types and generics.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Task<T> = () => T | Promise<T>; | |
type Mapper<T, R> = (instance: LoadingInstance<T>) => R; | |
interface LoadingInstance<T> { | |
result: T | undefined; | |
loading: boolean; | |
loadingError: Error | null; | |
reload: () => void; | |
} | |
/* FIRST SCENARIO - USING OVERLOADS */ | |
// first overload - no mapper | |
function useLoading<T>( | |
task: Task<T>, | |
dependencies: any[], | |
loadingDelay?: number | |
): LoadingInstance<T>; | |
// second overload - with mapper | |
function useLoading<T, R>( | |
task: Task<T>, | |
dependencies: any[], | |
loadingDelay: number | undefined, | |
mapper: Mapper<T, R> | |
): R; | |
// implementation | |
function useLoading<T, R>( | |
task: Task<T>, | |
dependencies: any[] = [], | |
loadingDelay = 500, | |
mapper?: Mapper<T, R> | |
): LoadingInstance<T> | R { | |
throw new Error('NOT IMPLEMENTED'); | |
} | |
/* Preparing some data for testing the typings */ | |
const doTask = () => ({ listen: 'song' }); | |
type DoTaskResult = ReturnType<typeof doTask>; | |
function extractListen(instance: LoadingInstance<DoTaskResult>) { | |
const { result } = instance; | |
return result && result.listen; | |
} | |
/* First test - OK */ | |
// usage 1 - expected result: LoadingInstance<DoTaskResult> (OK) | |
const result1 = useLoading(doTask, []); | |
// usage 2 - expected result: string | undefined (OK) | |
const listen1 = useLoading(doTask, [], undefined, extractListen); | |
/* SECOND SCENARIO - USING CONDITIONAL TYPES */ | |
type ExtractLoadingInstance<T, TMapper> = | |
TMapper extends Mapper<T, infer R> ? R | |
: LoadingInstance<T>; | |
function useLoading2<T, TMapper extends Mapper<T, any>>( | |
task: Task<T>, | |
dependencies: any[] = [], | |
loadingDelay = 500, | |
mapper?: TMapper | |
): ExtractLoadingInstance<T, TMapper> { | |
throw new Error('NOT IMPLEMENTED'); | |
} | |
// usage 1 - expected result: LoadingInstance<DoTaskResult> (FAIL - result is any) | |
const result2 = useLoading2(doTask, []); | |
// usage 2 - expected result: string | undefined (OK) | |
const listen2 = useLoading2(doTask, [], undefined, extractListen); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment