来自自定义映射类型的中间类型

时间:2018-10-19 16:29:24

标签: typescript

我创建了一个映射类型,以明确禁止某些对象/接口属性。原因是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'."

1 个答案:

答案 0 :(得分:2)

您遇到了一个约束,其中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>;

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

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

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


对于第二个问题,您想知道是否可以不使用intersectionDisallow)而不是union&)来代表| 。答案是肯定的,只要您的意思是最终输出不包含&,并且不表示定义根本不使用交集:

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

这实际上是等效的,只是它是mapped type,它在Omit<T, K> & Never<K>的键上进行迭代。它们与K | keyof T相同,但是使用交集具有使它成为homomorphic映射类型的优点,其中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;
}

对我很好。好的,希望能有所帮助;祝你好运!