如何拒绝TypeScript中的任意键?

时间:2019-10-01 08:46:20

标签: typescript

我正在为我的React + Apollo项目使用TypeScript,并且正在使用graphql-code-generator生成以下类型:

type Maybe<T> = T | null;

type Scalars = {
  ID: string,
  String: string,
  Boolean: boolean,
  Int: number,
  Float: number,
  DateTime: any, 
  ISO8601DateTime: any,
  Date: any,
  Json: any,
};

type RelayNode = {
  id: Scalars['ID'],
};

type Project = RelayNode & {
   __typename?: 'Project',
  number: Scalars['Int'],
  title: Scalars['String']
  projectManagers?: Maybe<Array<{ id: Scalars['String'], name: Scalars['String'] }>>,
};

type GetProjectQuery = (
  { __typename?: 'Query' }
  & { project: Maybe<(
    { __typename?: 'Project' }
    & Pick<Project, 'number' | 'title' | 'projectManagers'>
    )> }
);

type ProjectInput = {
  title?: Maybe<Scalars['String']>,
  number?: Maybe<Scalars['Int']>,
  projectManagerIds?: Maybe<Array<Scalars['String']>>,
};

现在,我想构建一个以project作为初始值的表单。一个示例项目可能看起来像...

const project: GetProjectQuery['project'] = {
    __typename: 'Project',
    number: 123,
    title: 'My awesome project',
    projectManagers: [{ id: '123', name: 'Me & myself'}]
}

...但是我的表单仅允许ProjectInput而不允许Project(因为输入变量可能与输出变量不同),所以我正在这样做...

const input: ProjectInput = project

...但是这似乎是有效的– Typescript不会引发任何错误。但是我要强制执行警告,该对象不应定义projectManagers。目标是强制未定义projectManagers但定义projectManagerIds的输入对象。

我创建了一个最小的测试用例here,其中“拒绝”有效:

type Project =  {
  title?: string
  number?: number
};

const project: Project = {
  title: 'foo',
  number: 123,
  foo: 'bar' // this key isn't allowed
}

但是我不明白为什么上面的生成类型无法使用这种方法。

Here's a complete playground

1 个答案:

答案 0 :(得分:1)

您不能在变量上强制执行此操作。 OOP的基本原理是可以将子类型分配给基本类型引用。由于打字稿使用结构化打字,因此基本类型/子类型的关系不是显式的,但是鉴于GetProjectQuery['project']ProjectInput的结构,GetProjectQuery['project']ProjectInput的子类型

现在,对于非常特定的情况,打字稿有时会故意违反该子类型,而该子类型可分配给基本类型规则:

  1. 多余的属性检查-当将对象文字直接分配给类型化引用并且不允许任何额外的属性时,这些检查就会启动。
  2. 对于弱类型(不具有强制属性的类型),如果类型之间没有重叠,则打字稿会发出警告。

您的情况都不是这样,所以您不会出错。

如果要在调用函数时执行此验证,那么我们可以使用一些通用类型参数魔术来捕获参数的实际类型并在任何其他属性上强制执行错误:

function withProjectInput<T extends ProjectInput>(p: T & Record<Exclude<keyof T, keyof ProjectInput>, ["No excess properties allowed here"]>) {

}
withProjectInput(project); // error here

play