部分<t>,但扩展了方法

时间:2019-05-23 13:20:03

标签: typescript typescript-generics

我想创建泛型(使用参数T),该类型具有以下行为:

  • 如果KT的键,则允许值类型T[K] | ((params?: Partial<T>) => string)
  • 如果K不是T的键,则允许值类型((params?: Partial<T>) => string)

我尝试了以下代码:

type PartialOrFunction<T> = {
  [K in keyof T]?: T[K] | ((params?: Partial<T>) => string)
} & {
  [K: string]: ((params?: Partial<T>) => string);
};

但是它拒绝Partial<T>TS2345)。

然后我尝试了以下代码:

type PartialOrFunction<T> = {
  [K in keyof T]?: T[K] | ((params?: Partial<T>) => string)
} & {
  [K: string]: ((params?: Partial<T>) => string) | any;
};

但是它不会对无关属性进行类型检查(因为它允许any)。

1 个答案:

答案 0 :(得分:1)

如果要使用我在评论中提到的PartialOrFunction的通用约束版本,请在此处。

您的PartialOrFunction<T>是我想调用DefaultProperty<T, D>的特定情况,它采用没有索引签名的对象类型T并添加了{类型的“默认”属性{1}}。这与字符串索引签名不同,后者要求D中的所有属性都可分配给T。但是正如您所看到的,TypeScript不支持该功能。

为此,您需要negate types才能将Dkeyof T中排除,并且需要使用诸如{ {3}}。目前您无法执行任何操作。因此,对于string,您想要说类似:

DefaultProperty<T, D>

the key type of an index signature。可能最接近的是

// this is not valid TypeScript (yet?)
type DefaultProperty<T, D> = T & { [k: string & not keyof T]: D }

但是它太宽了,因为它允许多余的属性出现在您的附加属性中。如果可以处理,那就太好了。否则,请继续阅读:

所以您 可以做的是创建一个类型为type MakeshiftDefaultProperty<T, D> = T & {[k: string]: D | T[keyof T]} 的类型VerifyDefaultProperty<T, D, C>,该类型采用一个候选人类型为C并返回一个新类型像DefaultProperty<T, D>。如果C可分配给VerifyDefaultProperty<T, D, C>,则C是有效的DefaultProperty<T, D>。如果C无法分配给VerifyDefaultProperty<T, D, C>,则它们不同的地方就是问题所在的C中的地方。

// caveat: T should probably not already have an index signature
type VerifyDefaultProperty<T extends object, D, C> =
    { [K in keyof C]: K extends keyof T ? T[K] : D }

例如:

interface T {x: number, y: string};
type D = boolean;
interface CGood {x: 0, y: "y", z: true};
type VGood = VerifyDefaultProperty<T, D, CGood>;
// type VGood = {x: number, y: string, z: boolean}
// CGood is assignable to VGood: yay!
interface CBad {x: 0, y: "y", z: true, oops: 123};
type VBad = VerifyDefaultProperty<T, D, CBad>;
// type VBad = {x: number, y: string, z: boolean, oops: boolean}
// CBad is *not* assignable to VBad: boo!
// specifically, CBad['oops'] is not assignable to boolean

现在,由于TypeScript不支持DefaultProperty<T, D>,所以它也不支持PartialOrFunction。但是我们可以使用VerifyDefaultProperty<T, D, C>并从中获得一个VerifyPartialOrFunction<T, C>,其作用方式相同。也就是说,当且仅当C是有效的VerifyPartialOrFunction<T, C>并且可以在错误消息中使用任何偏差时,C才可以分配给PartialOrFunction

type VerifyPartialOrFunction<T, C> = VerifyDefaultProperty<
    { [K in keyof T]?: T[K] | ((params?: Partial<T>) => string) },
    ((params?: Partial<T>) => string),
    C
>;

最后,我们引入了一个辅助函数,该函数接受类型为C的参数并返回它,但是如果C不是有效的VerifyPartialOrFunction<T, D, C>则抛出编译时错误。请注意,此函数必须为but you can't,因为您希望手动传递类型T并自动推断类型C,但是TypeScript不支持curried作为TS3.5。因此,我们需要将其分为两个功能:

const verifyPartialOrFunction = <T>() =>
    <C>(x: C & VerifyPartialOrFunction<T, C>): C =>
        x;

现在,让我们对其进行测试:

// Tests
interface Foo {
    a: string,
    b: number,
    c: boolean
}

const goodPartialOrFunctionFoo = verifyPartialOrFunction<Foo>()({
    a: (x?: Partial<Foo>) => "a",
    b: 1,
    w: () => "w"
}); // okay

const badPartialOrFunctionFoo = verifyPartialOrFunction<Foo>()({
    a: 1, // error!
    // Type 'number' is not assignable to type 
    // 'number & ((params?: Partial<Foo> | undefined) => string)'.
    b: (x: string) => "oops", // error!
    // Type '(x: string) => string' is not assignable to type 
    // '((x: string) => string) & 
    //  (number) | ((params?: Partial<Foo> | undefined) => string))'.
    w: "w"; // error!
    //  Type 'string' is not assignable to type 
    // 'string & (params?: Partial<Foo> | undefined) => string'.
})

看起来像您想要的行为。


就目前而言,这很好。如您所见,它拖累了很多类型系统,而依赖于PartialOrFunction的每个值/函数/类型现在都必须处理一个额外的泛型类型参数。这可能很乏味,因此我通常只建议用户对使用您的库调用的公开API进行此类工作。在您的库中,您应该仔细使用扩展的类型,例如

type MakeshiftPartialOrFunction<T> = Partial<T> & { [k: string]: 
  T[keyof T] | undefined | ((params?: Partial<T>) => string)
}

并在知道安全的情况下使用partial type parameter inference,但编译器则不然。


type assertions

希望有所帮助;祝你好运!