Typescript 泛型参数上的类型加宽丢失类型安全性

Typescript 泛型参数上的类型加宽丢失类型安全性,typescript,Typescript,我已经用这个代码创建了 导出类型规范={ 道具:弦; getters?:记录字符串>; } 出口类物品{ 建造师( 公共名称:string, 专用规范:S, ) {} } 导出类型interactiorInstance=Thing; 导出类型GetterImplementation={ [P]扩展((元素:E,…参数:未知[])=>未知)?(值:字符串)=>交互实例:从不; } 导出类型InteractiorType= ((值:字符串)=>InteractitorInstance)& 获取实现;

我已经用这个代码创建了

导出类型规范={
道具:弦;
getters?:记录字符串>;
}
出口类物品{
建造师(
公共名称:string,
专用规范:S,
) {}
}
导出类型interactiorInstance=Thing;
导出类型GetterImplementation={
[P]扩展((元素:E,…参数:未知[])=>未知)?(值:字符串)=>交互实例:从不;
}
导出类型InteractiorType=
((值:字符串)=>InteractitorInstance)&
获取实现;
导出函数createThing(名称:string){
返回函数(规格:S){
const result=函数(值:字符串):Thing{
让事物=新事物(名称、规格);
归还物;
}
以InteractiorType返回结果;
}
}
const Link=createThing('Link')({
道具:“a”,
吸气剂:{
byThis:(element)=>element.href,
by:(element)=>element.title
},
谁知道这是什么:666//不应该在这里
});
//这就是Spec需要是泛型类型参数的原因
//我做了一些恶作剧,把这些道具添加到这个东西上
Link.byThat('bb');
Link.byThis('cc'))
我是否可以使
Spec
仅具有
Spec
的键,即除
prop
getters
之外的任何内容无效


我需要Spec作为类型参数,因为它在
SpecImplementation

中的条件类型中使用。如果Spec需要强类型,那么为什么S扩展Spec?我尝试了以下方法,返回的类型不允许除了道具和getter之外的任何东西

导出函数createThing(名称:string){
返回函数(规格:Spec){
const result=函数(值:字符串):Thing{
让事物=新事物(名称、规格);
归还物;
}
返回结果;
}
}

在这里查看我的分叉操场:

如果规范需要强类型,那么为什么S扩展规范?我尝试了以下方法,返回的类型不允许除了道具和getter之外的任何东西

导出函数createThing(名称:string){
返回函数(规格:Spec){
const result=函数(值:字符串):Thing{
让事物=新事物(名称、规格);
归还物;
}
返回结果;
}
}

在这里签出我的分叉操场:

据我所知,TS不支持对通用参数对象文本进行过多的属性检查。我想你要找的是确切的类型——这里讨论的是:

可能的情况是,由于不能将类型为never的参数指定给函数,因此有点粗糙。所以在这里我检查泛型,看看它是否符合标准,如果不符合,就阻止它。不过,错误消息并不是最漂亮的


类型EnforceKeyMatch=keyof I扩展keyof T?keyof T扩展keyof I?I:从来没有;
导出函数createThing(名称:string){
返回函数(规范:EnforceKeyMatch){
const result=函数(值:字符串):Thing{
让事物=新事物(名称、规格);
归还物;
}
以InteractiorType返回结果;
}
}
通过这种方式,我尝试强制参数强制KeyMatch,I(输入)的键扩展T(目标)的键,反之亦然。如果不满足规范的此条件,则会弹出一个never类型

因此,在这里,如果有任何额外属性,则类型now错误:

但移除该属性后,其功能正常:


据我所知,TS不支持对通用参数对象文本进行过多的属性检查。我想你要找的是确切的类型——这里讨论的是:

可能的情况是,由于不能将类型为never的参数指定给函数,因此有点粗糙。所以在这里我检查泛型,看看它是否符合标准,如果不符合,就阻止它。不过,错误消息并不是最漂亮的


类型EnforceKeyMatch=keyof I扩展keyof T?keyof T扩展keyof I?I:从来没有;
导出函数createThing(名称:string){
返回函数(规范:EnforceKeyMatch){
const result=函数(值:字符串):Thing{
让事物=新事物(名称、规格);
归还物;
}
以InteractiorType返回结果;
}
}
通过这种方式,我尝试强制参数强制KeyMatch,I(输入)的键扩展T(目标)的键,反之亦然。如果不满足规范的此条件,则会弹出一个never类型

因此,在这里,如果有任何额外属性,则类型now错误:

但移除该属性后,其功能正常:


正如前面指出的,
S扩展规范
允许额外的属性-这正是您想要避免的

但让我们看看为什么你需要准确地扩展规范。。。看起来您需要能够提供getter的自定义映射,而在
Spec
中定义为
Record
。好吧,那么这不是线索吗?让我们将规范本身设置为通用规范,通过此记录中的键进行参数化:

export type Spec<E extends Element, G extends string> = {
  prop?: string;
  getters?: Record<G, (element: E) => string>;
}
(事实上,我可能误解了这个映射类型的意图……如果您确实想要智能处理getter,那么它应该成为
Spec
的一部分-当前
Spec
不允许有一个带有额外参数的getter)

最后,函数本身将使用新的
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>;
  }
}
导出函数createThing(名称:string){
返回函数(规格:Spec){
const result=函数(值:字符串):Thing{
让事物=新事物(名称、规格);
归还物;
}
以InteractiorType返回结果;
}
}

如前所述,
S扩展了规范
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>;
  }
}
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