У меня есть класс TypeScript Foo
с fieldOne: number | string
и fieldTwo
, который будет типа Bar[]
, когда fieldOne
— это number
, и Baz[]
, когда fieldOne
— это string
. Оба поля предоставляются как параметры конструктора.
Большая часть функциональности класса основана на объединении Bar
и Baz
, но в одном месте мне нужно сузить тип до Bar
или Baz
. Есть ли способ использовать компилятор, чтобы обеспечить создание экземпляра Foo
только с помощью number
и Bar[]
или string
и Baz[]
. Затем в классе я могу сузить тип fieldTwo
до Bar[]
или Baz[]
, когда мне нужно?
Я могу добиться эффекта с помощью наследования, как показано здесь.
interface Bar {
foo: string;
bar: number;
}
interface Baz {
foo: string;
baz: number;
}
abstract class Foo<T extends number | string, U extends Bar[] | Baz[]> {
private fieldOne: T;
protected fieldTwo: U;
constructor(paramOne: T, paramTwo: U) {
this.fieldOne = paramOne;
this.fieldTwo = paramTwo;
}
generalFunction() {
console.info(this.fieldTwo.map(x => x.foo).join());
this.specificFunction();
}
protected abstract specificFunction(): void;
}
class FooBar extends Foo<number, Bar[]> {
specificFunction() {
console.info(this.fieldTwo.map(x => x.bar).join());
}
}
class FooBaz extends Foo<string, Baz[]> {
specificFunction() {
console.info(this.fieldTwo.map(x => x.baz).join());
}
}
но я это немного затянуто.
Я надеялся получить что-то более лаконичное с условными типами, как здесь
type FooContents<T extends number | string> = T extends number ? Bar : Baz;
class Foo<T extends number | string> {
private fieldOne: T;
private fieldTwo: FooContents<T>;
constructor(param1: T, param2: FooContents<T>) {
this.fieldOne = param1;
this.fieldTwo = param2;
}
generalFunction() {
console.info(this.fieldTwo.foo);
}
specificFunction() {
// somehow narrow the type, maybe based on this.fieldOne?
// if fieldOne is number do something with Bar[];
// if fieldOne is string do something with Baz[];
}
}
но я не могу заставить его работать так, как мне нужно.
Третий способ, здесь, объединяет два поля в один объект.
type FooParams = {one: number, two: Bar[]} | {one: string, two: Baz[]};
class Foo {
params: FooParams;
constructor(params: FooParams) {
this.params = params;
}
generalFunction() {
console.info(this.params.two.map(x => x.foo).join());
}
specificFunction() {
if (isNumberyBary(this.params)) this.processBars(this.params.two);
if (isStringyBazy(this.params)) this.processBazs(this.params.two);
}
processBars(bars: Bar[]) {
console.info(bars.map(x => x.bar).join());
}
processBazs(bazs: Baz[]) {
console.info(bazs.map(x => x.baz).join());
}
}
function isNumberyBary(x: FooParams): x is {one: number, two: Bar[]} {
return typeof x === 'number';
}
function isStringyBazy(x: FooParams): x is {one: string, two: Baz[]} {
return typeof x === 'number';
}
но опять же, это более затянуто, чем я надеялся.
(см. предыдущий комментарий, пожалуйста) Объявления классов не могут напрямую реализовывать объединения. Было бы намного проще, если бы Foo
содержал союз, а не был союзом. Я был бы склонен либо использовать наследование с объявлениями классов, как показано здесь , либо обойти это, используя выражения классов и утверждения типов, как показано здесь . Ни один из способов не является особенно эргономичным. number
просто не является дискриминантным типом. Вместо этого вы действительно хотите дискриминируемый профсоюз.
@jcalz Я добавил свои попытки. Я пошел по пути наследования, но пытался получить что-то более лаконичное с помощью одного класса, что побудило меня попробовать условные типы. Утверждения типов также являются хорошим подходом.
Да, абсолютно. Я опасался раздувать их слишком сильно, поскольку они довольно длинные. Я добавлю то, что, по моему мнению, является сутью, но да, не стесняйтесь редактировать наверняка.
И, чтобы быть уверенным, что мы на одной волне, я, вероятно, напишу ответ, в котором будет сказано: «Поддерживаемый способ сделать это — с помощью наследования; в противном случае вы захотите сделать свой класс объединением, которое не может поддерживаться напрямую». , и его можно эмулировать только с помощью утверждений типа», и я покажу примеры. Это полностью решает вопрос или чего-то не хватает?
Только что добавил третий подход, но, опять же, он не очень аккуратен. Да, мы на одной волне, такой ответ охватит все.
Не могли бы вы дать нам минимальный воспроизводимый пример, с которым мы могли бы поиграть? Сейчас это кажется словесным описанием кода, а не самого кода. И в заголовке все еще говорится об «условных типах».