打字稿“有条件的”类型

时间:2018-12-02 08:01:23

标签: typescript typescript-types conditional-types

我有一个简单的函数,原则上来说,它的工作很简单,但是几乎每次我处理数据时,类型描述都很差,需要类型声明。

功能:

const fetch_and_encode = <T, E extends Encoded, C>({ source, encode, context }: {
    source: Fetcher<T | E> | T | E,
    encode?: Encoder<T, E, C>,
    context?: C
}): E => {
    let decoded;
    if ( typeof source === 'function' ) {
        decoded = (source as Fetcher<T | E>)();
    } else {
        decoded = source;
    }
    if ( typeof encode === 'function' ) {
        return encode(decoded as T, context);
    } else {
        return decoded as E;
    }
};

引用的类型:

export type Encoded = number | string | ArrayBuffer // | Array<Encoded> | Map<string, Encoded>
export type Fetcher<T> = () => T;
export type Encoder<T, E extends Encoded, C> = (decoded: T, context?: C) => E;

它基本上有两个变量sourceencode,每个变量可以具有两种有效类型之一,导致4个状态。 source是基准点或检索基准点的函数。 encodeundefined或转换source的“结果”的函数。最后,这种组合必须产生(相对)简单类型的值Encoded

我试图通过几种不同的方式来改进类型定义,但是似乎无法避免需要类型断言。这些尝试与加深我对类型系统的理解以及清理实际定义一样。就是说,感觉像我应该能够足够紧密地指定类型以避免类型断言,而我想了解如何。

我第一次尝试使用联合似乎并没有真正改善类型定义:

const fetch_and_encode = <T, E extends Encoded, C>( {source, encode, context}: {
    source: Fetcher<T>;
    encode: Encoder<T, E, C>;
    context?: C;
} | {
    source: Exclude<T, Function>; // T could still be a subtype of Function
    encode: Encoder<T, E, C>;
    context?: C;
} | {
    source: Fetcher<E>;
    encode: undefined;
    context?: any;
} | {
    source: E;
    encode: undefined;
    context?: any;
}): E => {
    let decoded;
    if ( typeof source === 'function' ) {
        decoded = (source as Fetcher<T | E>)();
        // decoded = source(); // Cannot invoke an expression whose type lacks a call signature.  Type 'Fetcher<T> |
        //                     // Fetcher<E> | (Exclude<T, Function> & Function)' has no compatible call signatures.
    } else {
        decoded = source;
    }
    if ( typeof encode === 'function' ) {
        return encode(decoded as T, context);
        // return encode(decoded, context); // Argument of type 'T | E' is not assignable to parameter of type 'T'
    } else {
        return decoded as E;
        // return decoded; // Type 'T | E' is not assignable to type 'E'
    }
};

然后我尝试使用实际的条件类型,但也无济于事:

const fetch_and_encode = <T, E extends Encoded, C>({ source, encode, context }: {
    source: Fetcher<T | E> | T | E,
    encode: Encoder<T, E, C> | undefined,
    context: C | undefined
} extends { source: infer S, encode: infer N, context?: C }
    ? S extends Function // Ensure S isn't a Function if it also isn't a Fetcher
        ? S extends Fetcher<T | E>
            ? N extends undefined
                ? { source: Fetcher<E>; encode: undefined; context?: any; }
                : { source: Fetcher<T>; encode: Encoder<T, E, C>; context?: C; }
            : never
        : N extends undefined
            ? { source: E; encode: undefined; context?: any; }
            : { source: T; encode: Encoder<T, E, C>; context?: C; }
    : never
): E => {
    let decoded;
    if ( typeof source === 'function' ) {
        decoded = (source as Fetcher<T | E>)();
        // decoded = source(); // Cannot invoke an expression whose type lacks a call signature.  Type 'Fetcher<T> |
        //                     // Fetcher<E> | (T & Function)' has no compatible call signatures.
    } else {
        decoded = source;
    }
    if ( typeof encode === 'function' ) {
        return encode(decoded as T, context);
        // return encode(decoded, context); // Argument of type 'T | E' is not assignable to parameter of type 'T'
    } else {
        return decoded as E;
        // return decoded; // Type 'T | E' is not assignable to type 'E'
    }
};

我不确定从这里还能去哪里。

对于{{3}}(如下),我尝试了重载,它们解决了最初的问题,但是提出了一个使我感到困惑的新问题:

function fetch_and_encode<T, E extends Encoded, C>({ source, encode, context }: {
    //   ^^^^^^^^^^^^^^^^ Overload signature is not compatible with function implementation
    source: E;
    encode: undefined;
    context?: any;
}): E;
function fetch_and_encode<T, E extends Encoded, C>({ source, encode, context }: {
    source: Fetcher<E>;
    encode: undefined;
    context?: any;
}): E;
function fetch_and_encode<T, E extends Encoded, C>({ source, encode, context }: {
    source: Fetcher<T>;
    encode: Encoder<T, E, C>;
    context?: C;
}): E;
function fetch_and_encode<T, E extends Encoded, C>({ source, encode, context }: {
    source: Exclude<T, Function>; // T could still be a subtype of Function
    encode: Encoder<T, E, C>;
    context?: C;
}): E {
    let decoded;
    if ( typeof source === 'function' ) {
        decoded = source();
    } else {
        decoded = source;
    }
    if ( typeof encode === 'function' ) {
        return encode(decoded, context);
    } else {
        return decoded;
    }
}

如果我将当前(通用)定义作为默认值添加,则上述错误消失了,但是再次需要类型断言。

1 个答案:

答案 0 :(得分:1)

这是超载时的处理方法。请注意,实际的函数主体是无类型的,我找不到使它正常工作的好方法(并且不确定是否有可能)。但是函数调用的类型正确。

function isFetcher<T>(obj: T | Fetcher<T>): obj is Fetcher<T> {
  return typeof obj === "function";
}

function fetchAndEncode<A extends Encoded>(source: A | Fetcher<A>): A;
function fetchAndEncode<A, B extends Encoded, C>(source: A | Fetcher<A>, encode: Encoder<A, B, C>, context?: C): B;
function fetchAndEncode(source: any, encode?: any, context?: any) {
  const datum = isFetcher(source) ? source() : source;
  return encode ? encode(datum, context) : datum;
}

这将通过以下类型测试:

let numericValue: number;

fetchAndEncode(numericValue); // number
fetchAndEncode(true); // error
fetchAndEncode(numericValue, val => `Hello ${val}`); // string
fetchAndEncode(() => numericValue); // number
fetchAndEncode(() => true); // error
fetchAndEncode(() => numericValue, val => `Hello ${val}`); // string