如何在实例化允许类型值或派生类型值的类时指定泛型类型



为什么这个 TypeScript 代码...

interface Base {
prop1: string
}
interface Derived extends Base {
prop2: string
}
class Collection<T> {
items: T[] = []
add(item: T): T {
this.items.push(item)
return item
}
}
const col = new Collection<Base>()
col.add({
prop1: 'value1',
prop2: 'value2'
})
// Note this work around, which demonstrates that this is perfectly workable without the typing
console.log(
(
col.add({
prop1: 'value1',
prop2: 'value2'
} as any) as any
)
.prop2
)

(看这里)

。产生此错误...

Argument of type '{ prop1: string; prop2: string; }' is not assignable to parameter of type 'Base'.
Object literal may only specify known properties, but 'prop2' does not exist in type 'Base'. Did you mean to write 'prop1'?

更具体地说,如何指定要创建一个接受类型为Base的项以及任何和所有派生接口类型(但不包括其他类型的)的Collection实例,而无需指定所有派生类型的联合?

根据到目前为止的回复,我猜这在当前(4.5.4)版本的TypeScript中是不可能的。

完全侧步您的代码示例的"为什么",我认为这与WeakMap或扩展接口没有任何关系。所以我将大大简化这个问题:

type A = { str: string }
const test1: A = { str: 'string', num: 123 }
// Type '{ str: string; num: number; }' is not assignable to type 'A'.
//  Object literal may only specify known properties, and 'num' does not exist in type 'A'.(2322)

打字稿用它的错误做了很多判断。在这里,它决定这可能是一个错误,因为您直接将对象文本分配给缺少其中一些文本属性的类型。这会导致类型系统"忘记"这些额外的属性,从而导致它们无法访问。

但是,这工作正常:

type A = { str: string }
const test2Data = { str: 'string', num: 123 }
const test2: A = test2Data

这是因为test2Data现在是它自己的值和类型,可以独立使用。testData.num的类型编号,但test2.num不存在(根据类型)。

操场


完全相同的规则在这里适用,这没有问题:

const map = new WeakMap<String, Base>()
const data = {
prop1: 'value1',
prop2: 'value2'
}
map.set('key1', data)

操场

>里拉;DR:当您将对象文本分配给会立即丢失该文本中的某些属性的类型时,打字稿会认为这是一个错误,因为您永远无法在初始分配后直接访问这些属性。

如果你真的想捕获额外的未知属性,你应该以类型安全的方式进行。就像泛型一样:

function makeThing<T extends { str: string }>(data: T): T {
return data
}
const thing = makeThing({ str: 'string', num: 123 })
thing.num // safe

操场


你的问题中的代码与我第一次回答时完全不同。

您可以通过使add()函数通用来"解决"这种情况。

class Collection<T> {
items: T[] = []
add<U extends T>(item: U): U {
this.items.push(item)
return item
}
}

现在不是add()接受T,而是接受TT或子类型。这会告诉 Typescript 额外的属性不会被丢弃,因为它们将由方法返回类型返回。

测试一下:

const col = new Collection<Base>()
const addedItem = col.add({
prop1: 'value1',
prop2: 'value2'
})
addedItem.prop1 // works
addedItem.prop2 // works
col.items[0].prop1 // works
col.items[0].prop2 // type error
;(col.items[0] as Derived).prop2 // works

但是,同样,这不是一个好主意。因为如果您实际上没有添加prop2因为add方法不需要它,那么您就无法防范假设该属性存在。

有目的地将数据填充到缺少该数据属性的类型中,只是在以后用不安全的强制转换将其捕获出来对我来说似乎是一个糟糕的计划。

操场

最新更新