我不知道如何在Typescript中输入这个。
我有一个Javascript类,我移植,简化在这里:
class A {
constructor ({
a = 'hi',
b = 5,
...rest
} = {}) {
this.a = a
this.b = b
this.extra = rest
}
}
构造函数接受带有一些默认值的options对象,并将其余的集合到一个对象中。该rest参数对象应该是平面,没有嵌套的对象或数组,并且需要JSON可序列化,并且是完全可选的。但是,如果存在,我希望为调用者提供一个机会,使其具有良好的类型。所以我定义了几个类型:
type KnownStuff = {
a: string
b: number
}
type FlatJSONCompatibleObject = {
[k: string]: string | number | boolean | null
} | {}
此外,我希望能够为其余部分指定一些额外的参数:
type Test = {
c: boolean
d: 'a' | 'b'
}
所以我尝试了以下操作:
class A <T extends FlatJSONCompatibleObject = {}> {
a: string
b: number
extra: T
constructor ({
a = 'hi',
b = 5,
...rest
}: Partial<KnownStuff> & T = {}) {
this.a = a
this.b = b
this.extra = rest // fails
}
}
哪个AFAICT失败,因为泛型没有约束。那么接下来:
class Foo <T extends Omit<FlatJSONCompatibleObject, keyof KnownStuff>> {
a: string
b: number
extra: T
constructor ({
a = 'hi',
b = 5,
...rest
}: Partial<KnownStuff> & T = {}) {
this.a = a
this.b = b
this.extra = rest
}
}
,但未能编译,我不能得到通用约束的正确。
我能做到的最接近的是:
class Bar<T extends Omit<FlatJSONCompatibleObject, keyof KnownStuff>> {
a: string
b: number
extra: Omit<FlatJSONCompatibleObject, keyof KnownStuff>
constructor ({
a = 'hi',
b = 5,
}: Partial<KnownStuff> = {}, rest: Partial<T> = {}) {
this.a = a
this.b = b
this.extra = rest
}
}
会编译,但会改变签名,我真的试图避免,因为这是为了工作,并且已经被多个其他团队广泛使用。这也是错误的:
const c = new Bar<Test>()
应该失败,因为参数上的属性不是可选的,但是我必须使用Partial<T>
才能分配默认的空对象。我怀疑我完全搞错了,这就是为什么我问这个问题。
那么问题的约束条件:
- 类接受一个带有一些已知属性的options对象。
- 这些已知的属性应该都有默认值,
new Whatever()
应该工作。 - 类还可以接受对象中的一些附加属性。
- 额外的东西,如果存在必须是JSON兼容和平面(没有嵌套的对象或数组)。
- 类的用户应该能够传递一个类型参数,以便额外的东西有一个定义良好的编译器强制类型,即
new Whatever<SomeTypeWithRequiredProperties>()
应该不工作。 - 我真的不想改变构造函数的签名(从Javascript POV),因为这已经被广泛使用。
如何输入?
游乐场,如果它有帮助
和一个(不正确,但希望给出要点)测试工具:
// class test harness
function test<T extends FlatJSONCompatibleObject = {}>(C: {new <U extends FlatJSONCompatibleObject = {}>(opts?: Partial<KnownStuff> & U): LP<T>}) {
// should work
const t1 = new C();
t1.extras // should be '{}'
const t2 = new C<{c: boolean}>({c: true});
t2.extras.c // should be true
// should be compile error
const t3 = new C<{c: boolean}>();
const t4 = new C<{c: boolean}>({d: 4});
}
我张贴这作为一个答案(而不是对问题的编辑),因为它至少部分回答了这个问题,但我不会接受它,因为a.我不完全理解为什么它的工作和b.我只能使它为函数工作,而不是一个类构造函数。感谢T.J. Crowder在评论中的帮助,我偶然发现了这个部分解决方案。
如果一个接口定义如下:
type LP<T extends FlatJSONCompatibleObject> = {
a: string
b: number
extras: T
}
,然后是像这样的重载函数:
function bar(): LP<{}>
function bar<T extends FlatJSONCompatibleObject>(opts: Partial<KnownStuff> & T): LP<T>
function bar(opts?: Partial<KnownStuff>): LP<{}>
{
const {
a = 'hi',
b = 5,
...extras
} = opts || {};
if (opts) {
return {
a,
b,
extras
}
} else {
return {
a,
b,
extras: {}
}
}
}
const bar1 = bar();
bar1.extras // {}
const bar2 = bar<{c: boolean}>({c: true})
bar2.extras.c // boolean
const bar3 = bar<{c: boolean}>() // compile error
const bar4 = bar<{c: boolean}>({d: boolean}) // compile error
这个可以工作,但是我不能让它为一个类工作:
class Foo <T extends FlatJSONCompatibleObject> {
public a: string
public b: number
// public extras: T | {}
public extras: T
constructor()
constructor(opts: Partial<KnownStuff> & T)
constructor(opts?: Partial<KnownStuff>)
{
const {
a = 'hi',
b = 5,
...extras
} = opts || {};
this.a = a
this.b = b
if (opts) {
this.extras = extras // error
} else {
this.extras = {} // error
}
}
}
虽然我可以更接近一点,但似乎通过显式地将额外参数设置为第二个参数:
class Bar<T extends FlatJSONCompatibleObject> {
public a: string
public b: number
public extras: T
constructor()
constructor(opts: Partial<KnownStuff>, extras: T)
constructor(opts?: Partial<KnownStuff>, extras?: never)
{
const {
a = 'hi',
b = 5.
} = opts || {}
this.a = a
this.b = b
if (extras) {
this.extras = extras
} else {
this.extras = {} // error!
}
}
}
我似乎仍然不能得到正确的过载。
游乐场
TL;DR
目前你还不能在100%类型安全的TypeScript中做到这一点。
细节:
我的TS之旅还相当早,但我认为这可能是一种情况:选择任何5(你的6).:-D我可以这样做,如果参数是可选的,但它不强制有一个参数,如果你提供一个类型参数与所需的属性(#1下面)。或者我可以在没有默认参数的情况下这样做(尽管我宁愿避免使用相对无害的@ts-ignore
)(下面的第2条)。或者稍微扭转一下我之前的一个尝试,它非常接近(下面的第3条),但extra
的类型是{} | x
,而不仅仅是x
。我询问了Titian Cernicova Dragomir,他确认你不能在今天的TypeScript中做到所有这些,并提供了下面的第4条,但像我的第1条一样,当你提供类型参数时,它也不会强制参数(同样,extra
的类型最终会变成Partial<something>
或Partial<{}>
)。
主要问题是您标记的:您不能将{}
分配给泛型属性,因为该泛型的具体类型可能不允许{}
。
所以很遗憾,看起来要选择最接近的东西。
# 1
这个不能正确处理new A<something>()
:
操场上联系
type KnownStuff = {
a: string
b: number
}
type FlatJSONCompatibleObject = {
[k: string]: string | number | boolean | null
}
type Test = {
c: boolean
d: 'a' | 'b'
}
class A <Extra extends FlatJSONCompatibleObject = {}> {
a: string
b: number
extra: Extra
constructor ();
constructor (props: Partial<KnownStuff> & Extra);
constructor (props?: any) {
const {a = 'hi', b = 5, ...extra} = props ?? {}
this.a = a
this.b = b
this.extra = extra
}
}
const a1 = new A() // All good
a1.extra // Type is {}
const a2 = new A<{c: boolean}>({d: "hi"}) // Error as desired
a2.extra // Type is {c: boolean}
const a3 = new A<{c: boolean}>() // No error :-(
a3.extra // Type is {c: boolean}
const a4 = new A<{c: boolean}>({c: true}) // All good
a3.extra // Type is {c: boolean}
# 2
这是一个不允许向构造函数传递任何参数的类型:
操场上联系
type KnownStuff = {
a: string
b: number
}
type FlatJSONCompatibleObject = {
[k: string]: string | number | boolean | null
}
type Test = {
c: boolean
d: 'a' | 'b'
}
class A <Extra extends FlatJSONCompatibleObject = {}> {
a: string
b: number
extra: Extra
constructor ({a = 'hi', b = 5, ...extra}: Partial<KnownStuff> & Extra) {
this.a = a
this.b = b
// @ts-ignore
this.extra = extra
}
}
// No longer relevant
// const a1 = new A()
// a1.extra // Type is {}
const a2 = new A<{c: boolean}>({d: "hi"}); // Error as desired
a2.extra // Type is {c: boolean}
const a3 = new A<{c: boolean}>(); // Error as desired
a3.extra // Type is {c: boolean}
const a4 = new A<{c: boolean}>({c: true}); // All good
a3.extra // Type is {c: boolean}
# 3
这是你对extra
,X | {}
而不是X
有一个尴尬的类型:
操场上联系
type KnownStuff = {
a: string
b: number
}
type FlatJSONCompatibleObject = {
[k: string]: string | number | boolean | null
}
type Test = {
c: boolean
d: 'a' | 'b'
}
class A <Extra extends FlatJSONCompatibleObject = {}> {
a: string
b: number
extra: Extra | {}
constructor (props?: Partial<KnownStuff> & Extra) {
if (props) {
const {a = 'hi', b = 5, ...extra} = props
this.a = a
this.b = b
this.extra = extra
} else {
this.a = 'hi'
this.b = 5
this.extra = {}
}
}
}
const a1 = new A() // All good
a1.extra // Type is {}
const a2 = new A<{c: boolean}>({d: "hi"}) // Error as desired
a2.extra // Type is {c: boolean} | {}
const a3 = new A<{c: boolean}>() // Error as desired
a3.extra // Type is {c: boolean} | {}
const a4 = new A<{c: boolean}>({c: true}) // All good
a3.extra // Type is {c: boolean} | {}
# 4
提香的Cernicova Dragomir有new A<something>()
问题#1,extra
的类型最终是Partial<{}>
或Partial<something>
:
操场上联系
type KnownStuff = {
a: string
b: number
}
type FlatJSONCompatibleObject = {
[k: string]: string | number | boolean | null
} | {}
type Test = {
c: boolean
d: 'a' | 'b'
}
class A <T extends FlatJSONCompatibleObject = {}> {
a: string
b: number
extra: Partial<T>
constructor ({
a = 'hi',
b = 5,
...rest
}: Partial<KnownStuff> & Partial<T> = {}) {
this.a = a
this.b = b
this.extra = rest as Partial<T>
}
}
const a1 = new A() // All good
a1.extra // Type is Partial<{}>
const a2 = new A<{c: boolean}>({d: "hi"}) // Error as desired
a2.extra // Type is Partial<{c: boolean}>
const a3 = new A<{c: boolean}>() // No error :-(
a3.extra // Type is Partial<{c: boolean}>
const a4 = new A<{c: boolean}>({c: true}) // All good
a3.extra // Type is Partial<{c: boolean}>