Другое более простое общее решение Ссылка на игровую площадку
class Dog {
public dogName: string = ""
public init(params: DogParams) { }
}
class Cat {
public catName: string = ""
public init(params: CatParams) { }
}
class DogParams { public dogValues: number = 0 }
class CatParams { public catValue: number = 0}
enum Kind {
DogKind = 'DogKind',
CatKind = 'CatKind',
}
const kindMap = {
[Kind.DogKind]: Dog,
[Kind.CatKind]: Cat,
};
type KindMap = typeof kindMap;
const paramsMap = {
[Kind.DogKind]: DogParams,
[Kind.CatKind]: CatParams,
}
type ParamsMap = typeof paramsMap;
type Tuples<T> = T extends Kind ? [T, InstanceType<KindMap[T]>, InstanceType<ParamsMap[T]>] : never;
type SingleKinds<K> = [K] extends (K extends Kind ? [K] : never) ? K : never;
type ClassType<A extends Kind> = Extract<Tuples<Kind>, [A, any, any]>[1];
type ParamsType<A extends Kind> = Extract<Tuples<Kind>, [A, any, any]>[2];
function getAnimalInstance<A extends Kind>(key:SingleKinds<A>, params: ParamsType<A>): ClassType<A> {
const animalKlass: ClassType<A> = kindMap[key];
const animalInstance = new animalKlass();
animalInstance.init(params);
return animalInstance;
}
// this works
const cat = getAnimalInstance(Kind.CatKind, new CatParams());
const shouldBeError = getAnimalInstance(Kind.DogKind, new CatParams()); // wrong params
const shouldBeErrorToo = getAnimalInstance(f(), new CatParams()); // undetermined kind
const shouldBeErrorAlso = getAnimalInstance(f(), new DogParams()); // undetermined kind
var k:Kind;
k = Kind.CatKind;
const suprisinglyACat = getAnimalInstance(k, new CatParams()); // even that works!
const shouldError = getAnimalInstance(k, new DogParams());
function f():Kind {
return Kind.DogKind;
}
И еще один пример этого, написанный для отражения моего другого ответа, который требовал ручной перегрузки. Он также автоматически получает типы Params без необходимости отдельной карты, определенной вручную.
Ссылка на игровую площадку
class DogParam { public n: number = 0; }
class CatParam { public n: string = "a"; }
class BatParam { public n: boolean = true; }
class Dog { init(p: DogParam) { } }
class Cat { init(p: CatParam) { } }
class Bat { init(p: BatParam) { } }
enum Kind { Dog, Cat, Bat }
const kindMap = {
[Kind.Dog]: Dog,
[Kind.Cat]: Cat,
[Kind.Bat]: Bat
}
type Tuples<K = Kind> = K extends Kind ? [
K,
InstanceType<(typeof kindMap)[K]>,
InstanceType<(typeof kindMap)[K]> extends
{ init: (a: infer P) => any } ? P : never
] : never;
type SingleKinds<K> = [K] extends (K extends Kind ? [K] : never) ? K : never;
type ClassType<K> = Extract<Tuples, [K, any, any]>[1];
type ParamsType<K> = Extract<Tuples, [K, any, any]>[2];
function a<K extends Kind>(k: SingleKinds<K>, p: ParamsType<K>): ClassType<K> {
var ins:ClassType<K> = new kindMap[k];
ins.init(p);
return ins;
}
function f(): Kind {
return Kind.Cat;
}
var k:Kind;
k = Kind.Cat;
a(Kind.Dog, new DogParam()); // works
a(Kind.Cat, new DogParam()); // error because mismatch
a(f(), new DogParam()); // error because kind undetermined
a(f(), new CatParam()); // error because kind undetermined
a(f() as Kind.Dog, new DogParam()); // works, but hey, it's your fault
// doing the wrong cast here manually
a(k, new CatParam()); // even this works
a(k, new DogParam()); // and this error
// you need to use exactly one kind at a time or it errors
var mixed: Kind.Dog | Kind.Cat = null as any;
var b = a(mixed, new DogParam());
var mixedfn = ():Kind.Dog | Kind.Cat => null as any;
var b = a(mixedfn(), new DogParam());
Решение, объединяющее как my, так и Taytay идей, которые генерируют все необходимое, от карты «виды до классов» и используют автоматически сгенерированную перегрузку функций, чтобы обеспечить хороший intellisense Ссылка на игровую площадку
class Dog {
public dogName: string = ""
public init(params: DogParams) { }
}
class Cat {
public catName: string = ""
public init(params: CatParams) { }
}
class DogParams { public dogValues: number = 0 }
class CatParams { public catValue: number = 0}
enum Kind {
DogKind = 'DogKind',
CatKind = 'CatKind',
}
const kindMap = {
[Kind.DogKind]: Dog,
[Kind.CatKind]: Cat,
};
type KindMap = typeof kindMap;
type Tuples<K = Kind> = K extends Kind ? [
K,
InstanceType<KindMap[K]>,
InstanceType<(typeof kindMap)[K]> extends
{ init: (a: infer P) => any } ? P : never
] : never;
type ClassType<K> = Extract<Tuples, [K, any, any]>[1];
type ParamsType<K> = Extract<Tuples, [K, any, any]>[2];
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type Fnc<T = Tuples> = UnionToIntersection<
T extends Tuples ? (key: T[0], p: T[2]) => T[1] : never
>;
var getAnimalInstance:Fnc = function<K extends Kind>(key: K, params:ParamsType<K>):ClassType<K> {
const animalKlass = kindMap[key];
const animalInstance = new animalKlass();
animalInstance.init(params);
return animalInstance;
}
// works
const cat = getAnimalInstance(Kind.CatKind, new CatParams());
// errors
const shouldBeError = getAnimalInstance((() => Kind.DogKind)(), new CatParams());
Пользователь Тайтай , задавший вопрос, исследовал этот код здесь Ссылка на игровую площадку , чтобы определить, как он работает.