首页 > 解决方案 > 部分的, 但用方法扩展

问题描述

我想创建泛型类型(带参数T),它允许以下行为:

我试过这段代码:

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)。

标签: typescripttypescript-generics

解决方案


如果您想要PartialOrFunction我在评论中提到的通用约束版本,就在这里。

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

为了做到这一点,您需要能够否定keyof T要从中排除的类型string,并且您需要能够使用这种否定类型作为索引签名的键类型。现在你也做不到。因此,对于DefaultProperty<T, D>想说的话:

// 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>并制作 a VerifyPartialOrFunction<T, C>,其作用方式相同。也就是说,当且仅当是有效的 时,C可分配给,并且任何偏差都可以在错误消息中使用:VerifyPartialOrFunction<T, C>CPartialOrFunction

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

最后,我们引入了一个辅助函数,它接受一个类型的参数并返回它,但如果不是有效C的,则会引发编译时错误。请注意,此函数必须是curried,因为您希望手动传入类型并自动推断类型,但 TypeScript 从 TS3.5 开始不支持部分类型参数推断。所以我们需要把它分解成两个函数:CVerifyPartialOrFunction<T, D, C>TC

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)
}

当您知道某些东西是安全的但编译器不安全时 ,使用类型断言。


链接到代码

希望有帮助;祝你好运!


推荐阅读