首页 > 解决方案 > 来自自定义映射类型的中间类型

问题描述

我创建了一个映射类型,用于明确禁止某些对象/接口属性。原因是 Typescript 的多余属性检查仅适用于直接分配对象文字时,而不适用于第一次分配给变量时。

请参阅“多余的属性检查” https://www.typescriptlang.org/docs/handbook/interfaces.html

我有一些函数接受一个不能包含某些特定的已知属性的对象。同样,这可以通过传入一个对象文字来解决,但这很容易被下一个开发人员忽略,所以我认为完全阻止它会很好(我确实意识到我应该检查对象是否有多余的道具运行时,但我的问题仅与 TS 有关)。

我的主要问题是是否可以编写Disallow类型以便我可以从中创建中间类型,例如DisallowBandC

第二个问题是是否Disallow可以在不创建两种类型的联合的情况下实现?也欢迎其他简化。

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

type Never<T, K extends keyof T> = { readonly [P in K]?: never };

// Can this be achieved without a union?
type Disallow<T, K extends keyof T> = Omit<T, K> & Never<T, K>;

interface Stuff {
  readonly a: string;
  readonly b?: number;
  readonly c: string | null;
  readonly d: null;
  readonly e?: null;
  readonly f?: undefined;
  readonly g: string;
}

type Blocked = 'b' | 'c'

// This works
export type Disallowed = Disallow<Stuff, Blocked>;

// This does not work:
export type DisallowBandC<T> = Disallow<T, Blocked>;

// TS Error:
// "Type 'Blocked' does not satisfy the constraint 'keyof T'.
//  Type '"b"' is not assignable to type 'keyof T'."

标签: typescript

解决方案


您遇到了一个约束,其中 的值K必须是 的键T。要解除此限制,您可以进行以下更改:

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

// don't need T at all here
type Never<K extends keyof any> = { readonly [P in K]?: never };

// don't need to restrict K to keys of T
type Disallow<T, K extends keyof any> = Omit<T, K> & Never<K>;

这现在可以工作了:

export type DisallowBandC<T> = Disallow<T, Blocked>;

请注意,由于您已经解除了约束 that K extends keyof T,您可以自由指定T根本不包含Blocked键的 a :

type IsThisOkay = DisallowBandC<{foo: string}>; 
// equivalent to {foo: string, b?: never, c?: never}

我想这就是你想要的,对吧?


对于第二个问题,您想知道是否可以表示Disallow没有交集( &),而不是联合( |)。答案是肯定的,只要你的意思是最终输出不包含 a&而不是定义根本不使用交集:

type Disallow<T, K extends keyof any> = {
  [P in keyof (Omit<T, K> & Never<K>)]: P extends K ? never : P extends keyof T ? T[P] : never
};

或者等价地,这使得很明显你肯定在定义中使用了相同的相交类型:

type Disallow<T, K extends keyof any> = {
  [P in keyof (Omit<T, K> & Never<K>)]: (Omit<T, K> & Never<K>)[P]
}

这本质上是等价的,除了它是一个映射类型,它遍历Omit<T, K> & Never<K>. 这些与 相同,K | keyof T但使用交集的优点是使其成为同态映射类型,其中 TypeScript 保留了readonly来自T. 让我们确保:

type TryItOut = DisallowBandC<Stuff>;

这检查为

type TryItOut = {
    readonly a: string;
    readonly d: null;
    readonly e?: null | undefined;
    readonly f?: undefined;
    readonly g: string;
    readonly b?: undefined;
    readonly c?: undefined;
}

对我来说看上去很好。好的,希望有帮助;祝你好运!


推荐阅读