【问题标题】:Typescript generics strange behavior打字稿泛型奇怪的行为
【发布时间】:2021-10-28 22:46:39
【问题描述】:

你知道为什么 Typescript 编译器 (4.3.5) 可以使用 mergeAll1 方法但在 mergeAll2 上失败吗?

非预期结果类型Map2<T> & Map2<Base> 不是很方便:(

export type ActionReducerX<T> = () => T;

export type Map1<T> = {
  [key in keyof T]: T[key];
};

export type Map2<T> = {
  [key in keyof T]: ActionReducerX<T[key]>;
};

interface Base {
  a: { aa: string };
}

type WithBase<T> = T & Base;

export function mergeAll1<T>(t: Map1<T>): Map1<WithBase<T>> {
  const b: Map1<Base> = {
    a: { aa: '' }
  };

  return {
    ...t,
    ...b
  };
}

export function mergeAll2<T>(t: Map2<T>): Map2<WithBase<T>> {
  const b: Map2<Base> = {
    a: () => ({ aa: '' })
  };

  // error TS2322: Type 'Map2<T> & Map2<Base>' is not assignable to type 'Map2<WithBase<T>>'.
  return {
    ...t,
    ...b
  };
}

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    主要问题在export type ActionReducerX&lt;T&gt; = () =&gt; T;

    似乎Map2&lt;WithBase&lt;T&gt;&gt;Map2&lt;T&gt; &amp; Map2&lt;Base&gt; 可以相互分配:

    declare var returnType: Map2<WithBase<Value>>
    declare var returnValue: Map2<Value> & Map2<Base>
    
    returnType = returnValue
    returnValue = returnType
    

    但当我们将它们与函数参数类型一起使用时,情况并非如此。

    export function mergeAll2<T>(t: Map2<T>): Map2<WithBase<T>> {
        const b: Map2<Base> = {
            a: () => ({ aa: '' })
        };
    
        let x: Map2<T> & Map2<Base> = null as any as Map2<T> & Map2<Base>;
        let y: Map2<WithBase<T>> = null as any as Map2<WithBase<T>>
    
        x = y // ok
        y = x // error
    
    
        const result = {
            ...t,
            ...b
        };
    
        return result // error
    }
    

    由于函数参数是逆变的,我们不再允许将x 分配给yMap2&lt;T&gt; &amp; Map2&lt;Base&gt; 分配给Map2&lt;WithBase&lt;T&gt;&gt;)、The arrow of inheritance points in the opposite direction

    有一个解决方法:

    export type ActionReducerX<T> = <U extends T>() => U;
    
    export type Map2<T> = {
        [key in keyof T]: ActionReducerX<T[key]>;
    };
    
    interface Base {
        a: { aa: string };
    }
    
    type WithBase<T> = T & Base;
    
    export function mergeAll2<T>(t: Map2<T>): Map2<WithBase<T>> {
        // remove explicit type
        const b = {
            a: () => ({ aa: '' })
        };
    
        return {
            ...t,
            ...b
        }
    }
    

    我们可以从一开始就使这个函数contravariant

    export type ActionReducerX<T> = <U extends T>() => U;
    
    type Animal = {
        name: string
    }
    
    type Dog = {
        paws: 4
    } & Animal
    
    type Check = Animal extends Dog ? true : false // false
    
    type Check2 = ActionReducerX<Animal> extends ActionReducerX<Dog> ? true : false // true
    

    尝试删除多余的U 泛型参数,您会看到继承行为将朝相反的方向改变。

    我对任何类型的批评持开放态度,因为这个话题(差异)对我来说仍然很难。

    Hereherehere 您可以找到有关此主题的更多信息

    更新ActionReducer的自定义包装器

    遗憾的是,我无法扩展 ActionReducer

    您可以创建自己的自定义类型包装器。见Wrapper

    export type ActionReducerX<T> = () => T;
    
    type Wrapper<T extends (...args: any) => any> = <U extends ReturnType<T>>() => U
    
    export type Map2<T> = {
        [key in keyof T]: Wrapper<ActionReducerX<T[key]>>;
    };
    
    interface Base {
        a: { aa: string };
    }
    
    type WithBase<T> = T & Base;
    
    export function mergeAll2<T>(t: Map2<T>): Map2<WithBase<T>> {
        // remove explicit type
        const b = {
            a: () => ({ aa: '' })
        };
    
        return {
            ...t,
            ...b
        }
    }
    
    type Animal = {
        name: string
    }
    
    type Dog = {
        paws: 4
    } & Animal
    
    type Check = Animal extends Dog ? true : false // false
    
    type Check2 = Wrapper<ActionReducerX<Animal>> extends Wrapper<ActionReducerX<Dog>> ? true : false // true
    

    Playground

    【讨论】:

    • 感谢您的详细解答。遗憾的是,我无法扩展 ActionReducer :(
    猜你喜欢
    • 2022-08-06
    • 1970-01-01
    • 2020-08-23
    • 1970-01-01
    • 2018-02-23
    • 2017-05-20
    • 2023-02-21
    • 2015-11-27
    • 2022-12-31
    相关资源
    最近更新 更多