【问题标题】:How to reject arbitrary keys in TypeScript?如何拒绝 TypeScript 中的任意键?
【发布时间】:2019-10-01 16:56:45
【问题描述】:

我在我的 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。目标是强制输入对象 hat 没有定义 projectManagersprojectManagerIds 定义。

我创建了一个最小的测试用例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.

【问题讨论】:

  • TS 只对文字赋值进行严格的属性检查。 IE。您分配= { ... } 的最后一个是“文字分配”,第一个= project 不是,因为您正在传递一个单独的变量。为了防止某些特定的不想要的键,即使是通过变量赋值,你可以明确地禁止它们:{ unwanted?: never }
  • 哦。哇。这让我大吃一惊。事实上,这是有效的:typescriptlang.org/play/#code/…。谢谢!

标签: typescript


【解决方案1】:

您不能对变量强制执行此操作。 OOP 的一个基本原则是一个子类型可以分配给一个基类型引用。由于 typescript 使用结构类型,所以基类型/子类型的关系并不明确,但给定GetProjectQuery['project']ProjectInput 的结构,GetProjectQuery['project']ProjectInput 的子类型

现在 typescript 有时会故意违反子类型可分配给特定场景的基本类型规则:

  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

【讨论】:

  • 非常感谢您的详细解释。你的withProjectInput对我帮助很大!
猜你喜欢
  • 2016-06-23
  • 1970-01-01
  • 1970-01-01
  • 2014-08-15
  • 2019-02-07
  • 2018-10-08
  • 2017-12-17
相关资源
最近更新 更多