首页 > 解决方案 > 定义一个数组,其推断类型与数组中的第一个道具相关

问题描述

我需要的是:

这是我的代码

enum HeroSex {
  male,
  female,
  unknown,
}

interface Hero {
  name: string;
  age: number;
  sex: HeroSex;
}

type reader<T> = (value: T) => void;

//the problem is 99% here :(
type readers<T, K extends keyof T> = [prop: K, readerFn: reader<T[K]>];

const readString: reader<string> = (value: string) => {
  console.log(`the string is ${value}`);
};

const readNumber: reader<number> = (value: number) => {
  console.log(`the number is ${value.toFixed()}`);
};

const readHeroSex: reader<HeroSex> = (value: HeroSex) => {
  console.log(`the enum is ${value.toString()}`);
};

const readProperties = <T>(Obj: T, readers: readers<T, keyof T>[]) => {
  for (const [prop, reader] of readers) {
    reader(Obj[prop]);
  }
};

const realHero: Hero = {
  name: "batman",
  age: 38,
  sex: HeroSex.male,
};

//expected result (for the moment is not working)
readProperties(realHero, [
  ["age", readNumber], //ok 
  ["name", readString], //ok 
  ["sex", readNumber],  //typescript error
]);

readProperties(realHero, [
  ["age", readNumber], //ok 
  ["name", readString], //ok 
  ["sex", readHeroSex],  //ok
]);

标签: typescripttypestypescript-generics

解决方案


请让我知道它是否适合您:

enum HeroSex {
   male,
   female,
   unknown,
}

interface Hero {
   name: string;
   age: number;
   sex: HeroSex;
}

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
   k: infer I
) => void
   ? I
   : never;

type UnionToOvlds<U> = UnionToIntersection<
   U extends any ? (f: U) => void : never
>;

type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never;

type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

type UnionToArray<T, A extends unknown[] = []> = IsUnion<T> extends true
   ? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
   : [T, ...A];


type MapPredicate<T> = T extends keyof Hero ? [T, (arg: Hero[T]) => any] : never

type Mapped<
   Arr extends Array<unknown>,
   Result extends Array<unknown> = []
   > = Arr extends []
   ? []
   : Arr extends [infer H]
   ? [...Result, MapPredicate<H>]
   : Arr extends [infer Head, ...infer Tail]
   ? Mapped<[...Tail], [...Result, MapPredicate<Head>]>
   : Readonly<Result>;
;


type Data<T> = Mapped<UnionToArray<keyof T>>

const makeData = <T,>(Obj: T, readers: Data<T>) => { };

const hero: Hero = {
   name: "batman",
   age: 38,
   sex: HeroSex.male,
};

const result1 = makeData(hero, [
   ['name', (arg: string) => null],
   ['age', (arg: number) => null],
   ['sex', (arg: HeroSex) => null]
])

const result2 = makeData(hero, [
   ['name', (arg: number) => null], // error
   ['age', (arg: number) => null],
   ['sex', (arg: HeroSex) => null]
])

在这里您可以找到更多解释。

致谢Shanon JacksonTitian Cernicova-Dragomir@jcalz

在我的示例中,您应该为所有键定义回调,如果您想让它更通用,您可以使用可选运算符。只需将上面的Mapperutil 替换为 next:

type Mapped<
   Arr extends Array<unknown>,
   Result extends Array<unknown> = []
   > = Arr extends []
   ? []
   : Arr extends [infer H]
   ? [...Result, MapPredicate<H>?] // <-- added question mark
   : Arr extends [infer Head, ...infer Tail]
   ? Mapped<[...Tail], [...Result, MapPredicate<Head>?]> // <--- added question mark
   : Readonly<Result>;
;

推荐阅读