以下代码生成以下TS错误:

‘字符串|布尔值’类型的参数不能赋给‘Never’类型的参数.

interface BaseAttribute <Value> {
  value: Value
  validate: (newValue: Value) => boolean
}

type StringAttribute = BaseAttribute<string> & { type: 'string' }

type BoolAttribute = BaseAttribute<boolean> & { type: 'bool' }

type Attribute = StringAttribute | BoolAttribute


const getAttribute = () : Attribute => {
  /* could return any kind of attribute, bool example there */
  return {
    type: 'bool',
    value: true,
    validate: (newBool: boolean) => !!newBool
  }
}

const attribute = getAttribute()
attribute.validate(attribute.value)
                   ~~~~~~~~~~~~~~~> error

我知道TS不知道传递的attribute.value参数来自相同的attribute,但有什么方法可以让它工作吗?

Click here for the TS playground

推荐答案

您会遇到TypeScrip缺乏对correlated unions的直接支持,就像在microsoft/TypeScript#30581中所描述的那样.就像你说的,attribute.validate(attribute.value)不起作用,因为TypeScrip没有跟踪attribute中的identity,只跟踪了type.因此,它判断代码就像您编写attribute1.validate(attribute2.validate)一样,因为Attributeunion type,所以它不能确保您没有将参数从一个联合成员传递给来自另一个联合成员的函数.在TypeScrip中直接调用带有参数联合的函数联合基本上是不可能的,而编译器不会抱怨.

在这种情况下,推荐的方法被描述为microsoft/TypeScript#47109,并且涉及到重构,以使用generics,constrained是联合的constrained,而不是联合本身.其 idea 是提出一个"基"对象类型,表示 struct 中随每个联合成员而变化的部分,然后按照indexed accesses到基类型或mapped typesmapped types的形式表示操作.我们的目标是,attribute.validate()将被视为参数与attribute.value的类型完全相同的单个函数类型.

对于您的示例,重构可能是这样的.基本对象类型为

interface AttributeMap {
  string: string,
  bool: boolean;
}

然后,您的Attribute类型可以表示为对AttributeMap以上的映射类型的通用索引:

type Attribute<K extends keyof AttributeMap = keyof AttributeMap> =
  { [P in K]: BaseAttribute<AttributeMap[P]> & { type: P } }[K]

表格{[P in K]: F<P>}[K]的一种类型被称为distributive object type,因为它将K中的并集转换为F<K>中的并集.我的意思是,如果KK1 | K2 | K3,那么{[P in K]: F<P>}[K]F<K1> | F<K2> | F<K3>.所以Attribute<keyof AttributeMap>相当于你原来的Attribute类型.由于default type argumentkeyof AttributeMap,这意味着当您提到没有类型参数的类型Attribute时,您得到的类型等同于原始的Attribute(允许我们保留一些原始代码不变).

但现在由于Attribute是通用的,您可以编写Attribute<"string">来恢复StringAttribute,编写Attribute<"bool">来恢复BoolAttribute.当我们使用泛型类型参数K时,Attribute<K>将被视为具有非并集validate属性和非并集value属性的东西.

attribute是类型Attribute时,您仍然不能只写attribute.validate(attribute.value).但是当它是泛型K的类型Attribute<K>时,你can就会这么做.这意味着我们应该将该代码包装在一个泛型函数中:

function validateAttribute<K extends keyof AttributeMap>(attribute: Attribute<K>) {
  attribute.validate(attribute.value); // okay
}

现在,您终于可以在Union类型的attribute上调用该函数了:

const attribute = getAttribute();
validateAttribute(attribute);

所以,你go .请注意,microsoft/TypeScript#47109中的方法不是microsoft/TypeScript#48730%类型安全的,仍然存在潜在的问题,您可能最终会欺骗编译器允许实际上不安全的代码;请参阅microsoft/TypeScript#48730.但这样做肯定要安全得多,而不是使用原始代码并使用type assertions来 suppress 错误.

Playground link to code

Typescript相关问答推荐

在Switch陈述中输入保护类

如果存在ts—mock—imports,我们是否需要typescpt中的IoC容器

如何将联合文字类型限制为文字类型之一

将对象属性转换为InstanceType或原样的强类型方法

使用泛型keyof索引类型以在if-condition内类型推断

为什么我的条件类型不能像我预期的那样工作?

我可以以这样一种方式键入对象,只允许它的键作为它的值吗?

嵌套对象的类型

如何在typescript中为this关键字设置上下文

打字错误推断

如何将类似数组的对象(字符串::匹配结果)转换为类型脚本中的数组?

Typescript .根据枚举输入参数返回不同的对象类型

获取类属性的类型';TypeScript中的getter/setter

通过辅助函数获取嵌套属性时保留类型

Typescript泛型-键入对象时保持推理

Typescript 和 Rust 中跨平台的位移数字不一致

我应该使用服务方法(依赖于可观察的)在组件中进行计算吗?

使用本机 Promise 覆盖 WinJS Promise 作为 Chrome 扩展内容脚本?

是否可以使用元组中对象的键来映射类型

有没有办法将类型与关键更改相匹配?