以某种方式避免在typescript中进行类型转换

以某种方式避免在typescript中进行类型转换,typescript,generics,casting,typescript-typings,Typescript,Generics,Casting,Typescript Typings,我想知道是否有可能使用类型保护或其他聪明的方法来确定我从函数中得到的类型,因为所有的TypeA对象都标记有类型:“TypeA”以及“TypeA”-对象中的类型。我希望避免类型转换的语句位于这个相当长的代码块的最后一行: type ControlPageType = "TypeA" | "TypeB"; interface ControlPageDto { type: ControlPageType; } interface TypeAData extends ControlPageDto

我想知道是否有可能使用类型保护或其他聪明的方法来确定我从函数中得到的类型,因为所有的TypeA对象都标记有类型:“TypeA”以及“TypeA”-对象中的类型。我希望避免类型转换的语句位于这个相当长的代码块的最后一行:

type ControlPageType = "TypeA" | "TypeB";

interface ControlPageDto {
  type: ControlPageType;
}

interface TypeAData extends ControlPageDto {
  foo: string;
}

interface TypeBData extends ControlPageDto {
  bar: number;
}

export interface ControlPageState<T extends ControlPageDto> {
  readonly type: ControlPageType;
  readonly checksum: string;
  readonly backendState: T;
}

export interface TypeA extends ControlPageState<TypeAData> {
  type: "TypeA";
}

export interface TypeB extends ControlPageState<TypeAData> {
  type: "TypeB";
}

export interface ControllerState<D extends ControlPageDto, S extends ControlPageState<D>> {
  pages: S[];
}

export interface ApplicationState {
  controllers: { [K in ControlPageType]: ControllerState<any, any> };
  currentChecksum: string;
}


export const findCurrentPage = (state: ApplicationState, type: ControlPageType): ControlPageState<any> => {
  const checksum = state.currentChecksum;

  if (checksum === null) {
    return null;
  }

  const controllerState = state.controllers[type];
  const pages = controllerState.pages;

  for (const bilde of pages) {
    if (bilde.checksum === checksum) {
      return bilde;
    }
  }

  return null;
};

const usage = (): void => {
  const sampleA1: TypeAData = { type: "TypeA", foo: "FOO" };
  const sampleA2: TypeAData = { type: "TypeA", foo: "OOF" };
  const sampleB1: TypeBData = { type: "TypeB", bar: 123 };

  const sampleData: ApplicationState = {
    controllers: {
      TypeA: {
        pages: [{ type: "TypeA", checksum: "1", customData: sampleA1 }, { type: "TypeA", checksum: "2", customData: sampleA2 }]
      },
      TypeB: { pages: [{ type: "TypeB", checksum: "A", customData: sampleB1 }] }
    },
    currentChecksum: "1"
  };

  const page = findCurrentPage(sampleData, "TypeA") as TypeA; // TODO Avoid type casting
};
type ControlPageType=“TypeA”|“TypeB”;
接口控制页到{
类型:ControlPageType;
}
接口类型数据将ControlPageDto扩展到{
foo:string;
}
接口类型数据将ControlPageDto扩展到{
条:数字;
}
导出接口控制页面状态{
只读类型:ControlPageType;
只读校验和:字符串;
只读后端状态:T;
}
导出接口类型A扩展了ControlPageState{
类型:“A型”;
}
导出接口类型B扩展了ControlPageState{
类型:“类型B”;
}
导出接口控制器状态{
页码:S[];
}
导出接口应用程序状态{
控制器:{[K in ControlPageType]:ControllerState};
当前校验和:字符串;
}
导出常量findCurrentPage=(状态:ApplicationState,类型:ControlPageType):ControlPageState=>{
常量校验和=state.currentChecksum;
如果(校验和===null){
返回null;
}
const controllerState=state.controllers[type];
const pages=controllerState.pages;
用于(共页){
if(bilde.checksum==校验和){
返回舱底;
}
}
返回null;
};
常量用法=():void=>{
const sampleA1:TypeAData={type:“TypeA”,foo:“foo”};
const sampleA2:TypeAData={type:“TypeA”,foo:“OOF”};
const sampleB1:TypeBData={type:“TypeB”,bar:123};
常量sampleData:ApplicationState={
控制器:{
A型:{
页面:[{type:“TypeA”,校验和:“1”,customData:sampleA1},{type:“TypeA”,校验和:“2”,customData:sampleA2}]
},
TypeB:{pages:[{type:“TypeB”,校验和:“A”,customData:sampleB1}]}
},
当前校验和:“1”
};
const page=findCurrentPage(sampleData,“TypeA”)作为TypeA;//避免类型转换
};

有几种方法可以做到这一点

最简单的方法是为每个常量提供多个重载:

export function findCurrentPage(state: ApplicationState, type: 'TypeA'): TypeA
export function findCurrentPage(state: ApplicationState, type: 'TypeB'): TypeB
export function findCurrentPage(state: ApplicationState, type: ControlPageType): ControlPageState<any> {
    //...
    return null;
};
或者,如果您希望更加安全,并避免有人错误地映射
“TypeA”:TypeB
,则可以声明一个帮助函数来约束映射有效。helper函数和helper变量只是用于帮助进行类型推断,没有与它们相关的运行时行为

// Helper to constrain each entry in the map type to have a property named type that has the same type P as the current key  
function typeMapValidator<T extends { [P in keyof T] : { type: P } }>() : T{return  null;}
// Helper will be null at runtime 
let mapHelper = typeMapValidator<{
    "TypeA": TypeA
    "TypeB": TypeB
    "TypeC": TypeA // Will be an error
}>()
type ControlPageTypeMap = typeof mapHelper;
type ControlPageType = keyof ControlPageTypeMap;
export function findCurrentPage<K extends ControlPageType>(state: ApplicationState, type: K): ControlPageTypeMap[K] {
    //...
    return null;
};
//帮助程序约束映射类型中的每个条目,使其具有名为type的属性,该属性的类型与当前键的类型P相同
函数typeMapValidator():T{return null;}
//助手在运行时将为空
设mapHelper=typeMapValidator()
类型ControlPageTypeMap=mapHelper的类型;
类型ControlPageType=ControlPageTypeMap的键;
导出函数findCurrentPage(状态:ApplicationState,类型:K):ControlPageTypeMap[K]{
//...
返回null;
};

wow。这既快又聪明,但不幸的是并不完全正确。给定findCurrentPage返回的内容(或者更确切地说是ControlPageState包含的内容),映射应该是:接口ControlPageTypeMap{“TypeA”:TypeAData“TypeB”:TypeBData},但即使这样,我还是得到了错误:error:(78,9)TS2322:Type“ControlPageState”不能分配给Type“TypeA”。属性“type”的类型不兼容。类型“TypeA”|“TypeB”不可分配给类型“TypeA”。类型“TypeB”不可分配给类型“TypeA”。在const page:TypeA=@MrMamen,我认为问题在于返回类型不应该是
ControlPageState
,而是直接
ControlPageTypeMap[K]
,因为ControlPageTypeMap包含直接从ControlPageState派生的类型。我测试过了,它看起来很有效,如果你有任何问题,请告诉我
// Helper to constrain each entry in the map type to have a property named type that has the same type P as the current key  
function typeMapValidator<T extends { [P in keyof T] : { type: P } }>() : T{return  null;}
// Helper will be null at runtime 
let mapHelper = typeMapValidator<{
    "TypeA": TypeA
    "TypeB": TypeB
    "TypeC": TypeA // Will be an error
}>()
type ControlPageTypeMap = typeof mapHelper;
type ControlPageType = keyof ControlPageTypeMap;
export function findCurrentPage<K extends ControlPageType>(state: ApplicationState, type: K): ControlPageTypeMap[K] {
    //...
    return null;
};