[英]type widening on generic parameter losing type safety
I have created this playground with this code我用这段代码创建了这个游乐场
export type Spec<E extends Element> = {
prop?: string;
getters?: Record<string, (element: E) => string>;
}
export class Thing<E extends Element, S extends Spec<E>> {
constructor(
public name: string,
private specification: S,
) {}
}
export type InteractorInstance<E extends Element, S extends Spec<E>> = Thing<E, S>;
export type GetterImplementation<E extends Element, S extends Spec<E>> = {
[P in keyof S['getters']]: S['getters'][P] extends ((element: E, ...args: unknown[]) => unknown) ? (value: string) => InteractorInstance<E, S> : never;
}
export type InteractorType<E extends Element, S extends Spec<E>> =
((value: string) => InteractorInstance<E, S>) &
GetterImplementation<E, S>;
export function createThing<E extends Element>(name: string) {
return function<S extends Spec<E>>(specification: S) {
const result = function(value: string): Thing<E, S> {
let thing = new Thing<E, S>(name, specification);
return thing;
}
return result as InteractorType<E, S>;
}
}
const Link = createThing<HTMLLinkElement>('link')({
prop: 'a',
getters: {
byThis: (element) => element.href,
byThat: (element) => element.title
},
whoCaresWhatThisIs: 666. // should not be here
});
// THis is why Spec needs to be a generic type argument
// I do some shenanigans in LocatorImplementation to add these props onto the Thing
Link.byThat('bb');
Link.byThis('cc')
Can I make the Spec
only have the keys of Spec
, ie anything other than prop
or getters
is invalid?我可以让
Spec
只有Spec
的键,即除了prop
或getters
之外的任何东西都是无效的?
I need Spec to be a type argument because it is used in a conditional type in SpecImplementation
我需要 Spec 作为类型参数,因为它在
SpecImplementation
的条件类型中使用
As pointed out earlier, S extends Spec
allows extra properties - exactly what you want to avoid!如前所述,
S extends Spec
允许额外的属性——正是你想要避免的!
But let's see why you need to extend Spec exactly... Looks like you need you be able to provide a custom map of getters, while in Spec
it's defined as Record<string, Function>
.但是让我们看看为什么您需要完全扩展 Spec ......看起来您需要能够提供自定义 map 的 getter,而在
Spec
中它被定义为Record<string, Function>
。 Ok - so isn't it the clue then?好的-那不是线索吗? Let's make Spec itself generic, parameterized by the keys in this record:
让我们使 Spec 本身成为通用的,由该记录中的键参数化:
export type Spec<E extends Element, G extends string> = {
prop?: string;
getters?: Record<G, (element: E) => string>;
}
Then we update all derived types correspondingly.然后我们相应地更新所有派生类型。 Note that
GetterImplementation
mapped type becomes simpler now, as it works directly with the new generic parameter:请注意,
GetterImplementation
映射类型现在变得更简单,因为它直接与新的泛型参数一起使用:
export type GetterImplementation<E extends Element, G extends string> = {
[P in G]: (value: string) => InteractorInstance<E, G>;
}
(in fact I might have misunderstood the intention with this mapped type... if you do want smart handling of getters, it should be made a part of Spec
- currently Spec
doesn't allow to have a getter with extra arguments). (事实上,我可能误解了这种映射类型的意图......如果你确实想要智能处理 getter,它应该成为
Spec
的一部分——目前Spec
不允许有一个带有额外参数的 getter)。
And finally, the function itself is gonna be generic with the new G extends string
generic parameter instead of Spec
:最后,function 本身将是通用的,使用新的
G extends string
通用参数而不是Spec
:
export function createThing<E extends Element>(name: string) {
return function<G extends string>(specification: Spec<E, G>) {
const result = function(value: string): Thing<E, G> {
let thing = new Thing<E, G>(name, specification);
return thing;
}
return result as InteractorType<E, G>;
}
}
If Spec needs to be strongly typed, then why S extends Spec?如果 Spec 需要强类型,那为什么 S 扩展 Spec 呢? I tried the following and the returned type didn't allow anything apart from props and getters,
我尝试了以下方法,返回的类型除了道具和吸气剂之外不允许任何东西,
export function createThing<E extends Element>(name: string) {
return function(specification: Spec<E>) {
const result = function(value: string): Thing<E, Spec<E>> {
let thing = new Thing<E, Spec<E>>(name, specification);
return thing;
}
return result;
}
}
Checkout my forked playground here: Playground Link在这里查看我的分叉游乐场: 游乐场链接
I made some change to your code in playground that I think solves your problem.我在操场上对您的代码进行了一些更改,我认为可以解决您的问题。
In your original code yuo had S extends Spec<E>
as an type argument you passed around.在您的原始代码中,您将
S extends Spec<E>
作为您传递的类型参数。 This meant that Type S
can allow extra properties.这意味着 Type
S
可以允许额外的属性。
However, by defining a Type argument T extends string
which you pass around, your function argument is of type Spec<E, T>
now instead of S extends Spec<E>
which means it can't allow extra properties.但是,通过定义您传递的 Type 参数
T extends string
,您的 function 参数现在是Spec<E, T>
类型而不是S extends Spec<E>
,这意味着它不能允许额外的属性。
I include the modified code here as well:我在这里也包含了修改后的代码:
export type Spec<E extends Element, T extends string> = {
prop?: string;
getters?: {
[key in T]: (element: E) => string;
}
}
export class Thing<E extends Element, T extends string> {
constructor(
public name: string,
private specification: Spec<E, T>,
) {}
}
export type InteractorInstance<E extends Element, T extends string> = Thing<E, T>;
// GetterImplementation is simpler now.
export type GetterImplementation<E extends Element, T extends string> = {
[P in T]: (value: string) => InteractorInstance<E, T>;
}
export type InteractorType<E extends Element, T extends string> =
((value: string) => InteractorInstance<E, T>) &
GetterImplementation<E, T>;
export function createThing<E extends Element>(name: string) {
return function<T extends string>(specification: Spec<E, T>) {
const result = function(value: string): Thing<E, T> {
let thing = new Thing<E, T>(name, specification);
return thing;
}
return result as InteractorType<E, T>;
}
}
// Type T here is infered as 'byThis'|'byThat'
const Link = createThing<HTMLLinkElement>('link')({
prop: 'a',
getters: {
byThis: (element) => element.href,
byThat: (element) => element.title,
},
whoCaresWhatThisIs: 666. // This now gives an error as desired
});
// THis is why Spec needs to be a generic type argument
// I do some shenanigans in LocatorImplementation to add these props onto the Thing
Link.byThat('bb'); // This work as expected now
Link.byThis('cc'); // This work as expected now
// Since Link is of Type InteractorType<HTMLLinkElement, 'byThat'|'byThis'>, byThose does not work
Link.byThose('dd'); // This also results in an error
I hope my answer solves your problem, and thank you for the question.希望我的回答能解决你的问题,谢谢你的提问。 Thinking about the solution helped learn something new.
思考解决方案有助于学习新知识。
As far as I understand it, TS doesn't support excess property checks for generic parameter object literals.据我了解,TS 不支持对通用参数 object 文字进行过多的属性检查。 I think what you're looking for is exact types - something that's in discussion here: https://github.com/microsoft/TypeScript/issues/12936
我认为您正在寻找的是确切的类型 - 这里正在讨论的内容: https://github.com/microsoft/TypeScript/issues/12936
What is possible is something a bit hacky on the basis that you can't assign a parameter which has a type never to a function.基于您无法将类型永远不会分配给 function 的参数,可能会有点 hacky。 So here I check the generic to see if it meets the criteria, and then block if it isn't.
所以在这里我检查泛型,看看它是否符合标准,如果不符合则阻止。 The error message isn't the prettiest though.
错误消息不是最漂亮的。
type EnforceKeyMatch<I extends {[KI in keyof T]: any }, T extends {[TI in keyof T]?: any }> = keyof I extends keyof T ? keyof T extends keyof I ? I : never : never;
export function createThing<E extends Element>(name: string) {
return function<S extends Spec<E>>(specification: EnforceKeyMatch<S, Spec<Element>>) {
const result = function(value: string): Thing<E, S> {
let thing = new Thing<E, S>(name, specification);
return thing;
}
return result as InteractorType<E, S>;
}
}
With this I try to enforce that for the params to EnforceKeyMatch, the keys of I (input) extend the keys of T (target), and vice versa.有了这个,我尝试强制执行 EnforceKeyMatch 的参数,I(输入)的键扩展 T(目标)的键,反之亦然。 This then spits out a never type if this condition is not met for specification.
如果规范不满足此条件,则它会吐出 never 类型。
So here with any extra property the type now errors:所以这里有任何额外的属性类型现在错误:
But with the property removed it functions as normal:但是删除该属性后,它可以正常工作:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.