代码之家  ›  专栏  ›  技术社区  ›  David Shortman

TypeScript4.0+是否能够使用映射变量元组类型的函数?

  •  1
  • David Shortman  · 技术社区  · 4 年前

    { a: value }

    const mapToMyInterface = (...things) => things.map((thing) => ({ a: thing}));
    

    中的Typescript尚无法为此函数的结果推断强类型:

    const mapToMyInterface = mapToInterface(1, 3, '2'); // inferred type{ a: any }[]
    

    首先,我定义了一个类型来描述映射到可观察对象的数组:

    type MapToMyInterface<T extends any[]> = {
      [K in keyof T]: T[K] extends Array<infer U> ? { a: U } : { a: T[K] }
    }
    

    const mapToMyInterface = <T extends any[]>(...things: T): MapToMyInterface<T> => things.map((thing) => ({ a: thing}));
    

    到目前为止,Typescript并不满意。函数的返回表达式被突出显示,错误为“TS2322:Type'{a:any;}[]”不能赋值给类型“MapToMyInterface”

    显然,参数 thing 需要在映射函数中显式键入。但我不知道怎么说“第n种”,这正是我需要的。

    也就是说,没有标记 事情 作为一个 T[number] 甚至可以做以下工作:

    const mapToMyInterface = <T extends any[], K = keyof T>(...things: T): MapToMyInterface<T> => things.map((thing: T[K]) => of(thing));
    

    这在打字机里能用吗?

    在@jcalz的回答后编辑: 对于后人来说,我想把我问题的最初动机,以及从@jcalz的答案中得到的解决方案。

    我在试着包装一个RxJs操作员, withLatestFrom ,懒散地评估传递到它中的可观察值(当您可能传入一个函数的结果时非常有用,该函数在某个地方启动了一个持续的订阅,例如 store.select

    我可以像这样成功地断言返回值:

    export const lazyWithLatestFrom = <T extends Observable<unknown>[], V>(o: () => [...T]) =>
      concatMap(value => of(value).pipe(withLatestFrom(...o()))) as OperatorFunction<
        V,
        [V, ...{ [i in keyof T]: TypeOfObservable<T[i]> }]
      >;
    
    2 回复  |  直到 4 年前
        1
  •  4
  •   jcalz    4 年前

    假设你有一个泛型函数 wrap() T 并返回类型为的值 {a: T} ,例如:

    function wrap<T>(x: T) {
        return ({ a: x });
    }
    

    things 还有电话 things.map(wrap) ,您将得到一个弱类型函数,如您所注意到的:

    const mapWrapWeaklyTyped = <T extends any[]>(...things: T) => things.map(wrap);
    // const mapWrapWeaklyTyped: <T extends any[]>(...things: T) => {a: any}[]
    

    {a: any} . 这是事实,但不是很有用:

    const weak = mapWrapWeaklyTyped(1, "two", new Date());
    try {
        weak[2].a.toUpperCase(); // no error at compile time, but breaks at runtime
    } catch (e) {
        console.log(e); // weak[2].prop.toUpperCase is not a function 
    }
    

    2 Date 而不是包装好的 string . 我必须等到运行时才能看到问题。


    standard TypeScript library's typing 对于 Array.prototype.map()

    interface Array<T> {
      map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
    }
    

    东西.地图(包装) ,编译器将推断单个 U {a:任何} 东西 T extends any[] ,所有编译器都知道 any

    一般的 你可以给我打字 这将处理 callbackfn type constructors ,当前不直接支持哪个TypeScript(请参见 microsoft/TypeScript#1213 相关功能请求)。


    回电 ,(例如。, (x: T) => {a: T} mapped array/tuple types .

    这里是:

    const mapWrapStronglyTyped = <T extends any[]>(...things: T) => things.map(wrap) as
        { [K in keyof T]: { a: T[K] } };
    // const mapWrapStronglyTyped: 
    //   <T extends any[]>(...things: T) => { [K in keyof T]: {a: T[K]; }; }
    

    K T 数组,然后 T[K] {a: T[K] } .

    map() 不预期此特定的泛型映射函数,必须使用 type assertion 进行类型检查。如果您只关心编译器在没有类型断言的情况下无法验证这一点,那么这实际上是在TypeScript中没有更高级类型的情况下所能做的最好的事情。

    您可以在与前面相同的示例中进行测试:

    const strong = mapWrapStronglyTyped(1, "two", new Date());
    try {
        strong[2].a.toUpperCase(); // compiler error, Property 'toUpperCase' does not exist on type 'Date'
    } catch (e) {
        console.log(e); //  strong[2].prop.toUpperCase is not a function 
    }
    // oops, I meant to do this instead!
    console.log(strong[1].a.toUpperCase()); // TWO
    

    现在编译器捕捉到错误并告诉我 日期 对象没有 toUpperCase 方法。万岁!


    type MapToMyInterface<T extends any[]> = {
      [K in keyof T]: T[K] extends Array<infer U> ? { a: U } : { a: T[K] }
    }
    

    有点奇怪,因为你在做地图 两次 ;除非传入数组,否则没有理由进行检查 T[K] K 是它的索引。所以我得说回来吧 {a: T[K]} 除非我错过了重要的事情。


    Playground link to code

        2
  •  1
  •   Linda Paiste    4 年前

    你能输入任意数量的不同类型的参数并返回一个数组吗 Observables 以相同的顺序排列? 不。 不能基于特定的数字键映射类型。

    你能输入这些相同的任意参数并返回一个数组吗 可观察的 对。

    type Unpack<T> = T extends (infer U)[] ? U : T;
    
    const mapToObservable = <T extends any[]>(...things: T): Observable<Unpack<T>>[] => things.map((thing) => of(thing));
    
    const mapped: Observable<string | number>[] = mapToObservable(1, 3, "2");
    

    Playground Link

    编辑: 可以按相同的顺序获取返回类型,如中所述 this answer .

    此返回类型为您提供所需的返回

    type MapToMyInterface<T extends any[]> = any[] & {
      [K in keyof T]: {a: T[K]};
    } & {length: T['length']};
    

    但返回的类型 array.map() 不可赋值给它(因为@jcalz刚刚解释过的原因),所以您必须使用 as .

    const mapToMyInterface = <T extends any[]>(...things: T): MapToMyInterface<T> => things.map((thing) => ({ a: thing})) as MapToMyInterface<T>;
    
    const mapped = mapToMyInterface(1, 3, "2");
    const [one, three, two] = mapped; // gets each element correctly
    

    Playground Link

    推荐文章