之后我将展示我的实际问题的详细示例。以下代码显示了一个简化的问题:
type Mapper<T, R> = (data: T) => R;
interface Config<T, R> {
readonly mapper?: Mapper<T, R>;
}
function transform<T, R>(config: Config<T, R>, data: T) {
return config.mapper?.(data) ?? data;
}
const mapper: Mapper<number, string> = (data: number) => `${data};`
const result1 = transform({}, 1); // I expect result1 to be of type number as no mapper is given.
const result2 = transform({ mapper }, 1); // I expect result2 to be of type string as a mapper is given.
如何正确输入
transform
的结果。请注意,函数 transform
没有意义。这只是一个最小的例子,以便稍后解释实际问题的要点。
我们假设给出以下代码:
export type PendingState = {
type: "PENDING";
};
export type SuccessState<T> = {
type: "SUCCESS";
data: T;
};
export type ErrorState = {
type: "ERROR";
error: unknown;
};
export type SuspenseState<T> = PendingState | SuccessState<T> | ErrorState;
export type PendingStateMapper<R> = () => R;
export type SuccessStateMapper<T, R> = (data: T) => R;
export type ErrorStateMapper<R> = (error: unknown) => R;
export interface Mappers<T, P, S, E> {
readonly pendingState?: PendingStateMapper<P>;
readonly successState: SuccessStateMapper<T, S>;
readonly errorState: ErrorStateMapper<E>;
}
export function fromSuspenseState<T, P, S, E>(mappers: Mappers<T, P, S, E> & { pendingState: PendingStateMapper<P> }): OperatorFunction<SuspenseState<T>, P | S | E>;
export function fromSuspenseState<T, P, S, E>(mappers: Mappers<T, P, S, E> & { pendingState?: undefined }): OperatorFunction<SuspenseState<T>, null | S | E>;
export function fromSuspenseState<T, P, S, E>(mappers: Mappers<T, P, S, E>): OperatorFunction<SuspenseState<T>, P | S | E | null> {
return (source$: Observable<SuspenseState<T>>): Observable<P | S | E | null> => {
return source$.pipe(
map((state) => {
switch (state.type) {
case "PENDING":
return mappers.pendingState ? mappers.pendingState() : null;
case "SUCCESS":
return mappers.successState(state.data);
case "ERROR":
return mappers.errorState(state.error);
}
})
);
};
}
目前我正在使用函数重载来正确推断返回类型。如果给出
PendingStateMapper
,则返回映射器的 结果,因此是 P
。如果未给出 PendingStateMapper
,则返回 null
。
我们假设
SuccessStateMapper
和 ErrorStateMapper
也应该是可选的。如果给出 SuccessStateMapper
则应返回 S
,否则应返回 T
。如果给出 ErrorStateMapper
则应返回 E
,否则应返回 unknown
。
使用当前的重载函数方法需要大量不同的签名才能覆盖所有可能的解决方案。
因此,我想知道是否可以通过不同的方式解决这个问题。我心里有这样的想法。不幸的是,以下代码无法正常工作:
export type PendingStateMapperResult<T, P, S, E, M extends Mappers<T, P, S, E>> = M extends { readonly pendingState: PendingStateMapper<P> } ? P : null;
export function fromSuspenseState<T, P, S, E, M extends Mappers<T, P, S, E>>(mappers: M): OperatorFunction<SuspenseState<T>, PendingStateMapperResult<T, P, S, E, M> | S | E> {
return (source$: Observable<SuspenseState<T>>): Observable<PendingStateMapperResult<T, P, S, E, M> | S | E> => {
return source$.pipe(
map((state) => {
switch (state.type) {
case "PENDING":
return mappers.pendingState ? mappers.pendingState() : null;
case "SUCCESS":
return mappers.successState(state.data);
case "ERROR":
return mappers.errorState(state.error);
}
})
);
};
}
它编译时为 TypeScript 提供了专门的提示。以下代码中的一行必须像这样调整:
return (mappers.pendingState?. () ?? null) as PendingStateMapperResult<T, P, S, E, M>;
不幸的是,使用
fromSuspenseState
并不能正确推断类型。
最后我想出了下面的代码,看起来可行。
type PendingStateMapperResult<T, P, S, E, M extends Mappers<T, P, S, E>> = M["pendingState"] extends PendingStateMapper<infer R> ? R : null;
type SuccessStateMapperResult<T, P, S, E, M extends Mappers<T, P, S, E>> = M["successState"] extends SuccessStateMapper<T, infer R> ? R : T;
type ErrorStateMapperResult<T, P, S, E, M extends Mappers<T, P, S, E>> = M["errorState"] extends ErrorStateMapper<infer R> ? R : unknown;
export type FromSuspenseStateResult<T, P, S, E, M extends Mappers<T, P, S, E>> =
| PendingStateMapperResult<T, P, S, E, M>
| SuccessStateMapperResult<T, P, S, E, M>
| ErrorStateMapperResult<T, P, S, E, M>;
export function fromSuspenseState<T, P, S, E, M extends Mappers<T, P, S, E>>(mappers: M): OperatorFunction<SuspenseState<T>, FromSuspenseStateResult<T, P, S, E, M>> {
return (source$: Observable<SuspenseState<T>>): Observable<FromSuspenseStateResult<T, P, S, E, M>> => {
return source$.pipe(
map((state) => {
switch (state.type) {
case "PENDING":
return (mappers.pendingState?.() ?? null) as PendingStateMapperResult<T, P, S, E, M>;
case "SUCCESS":
return (mappers.successState?.(state.data) ?? state.data) as SuccessStateMapperResult<T, P, S, E, M>;
case "ERROR":
return (mappers.errorState?.(state.error) ?? state.error) as ErrorStateMapperResult<T, P, S, E, M>;
}
})
);
};
}
在写和更新这个问题的过程中,我基本上自己找到了解决方案。我仍然想知道是否有一般的经验法则如何根据给定参数的属性推断结果类型。也许这要视情况而定。
我很感谢任何人分享他解决此类“问题”的经验。
要根据可选属性推断 TypeScript 中的类型,请使用条件类型和 infer 关键字。避免重载具有太多变体的函数;相反,使用条件类型以更易于维护和可读的方式处理不同的可能性。简而言之,您使用条件类型和推断的方法是合适的,并且可以使代码比过度函数重载更干净。