У меня есть функция, которая получает аргумент options
с атрибутом kind
. Возможные значения для kind
— это небольшой набор значений. Так что это в основном перечисление.
В зависимости от kind
функция должна иметь другое возвращаемое значение. В то время как все возможные возвращаемые значения расширяются от некоторого общего базового типа.
Я могу добиться того, чего хочу, с помощью перегрузок, но тогда сама функция не очень хорошо типизирована:
function test(options: Scenarios['bar']['options']): Scenarios['bar']['ret'];
function test(options: Scenarios['foo']['options']): Scenarios['foo']['ret'];
function test(options: any): any {
...
};
Есть ли хороший способ ввести это с помощью дженериков? Было бы идеально, если бы if(options.kind === 'foo') { return ... }
также корректно применял правильный тип возвращаемого значения.
Это то, что я пробовал, но это не работает.
type Base {
a: string;
}
type Foo {
b: string;
}
type Bar {
c: string;
}
interface Scenarios {
foo: { options: { kind: 'foo', input: string }, ret: Foo },
bar: { options: { kind: 'bar' }, ret: Bar },
}
function test<S extends keyof Scenarios, O extends Scenarios[S]['options'], R extends Scenarios[S]['ret']>(options: O): R {
const out: Partial<R> = {
a: 'one',
};
if(options.kind === 'foo') {
out.b = options.input;
}
if(options.kind === 'bar') {
out.c = "whatever"
}
return out;
}
Здесь ни O
, ни R
, похоже, не набраны правильно. Я получаю несколько ошибок:
a: 'one',
ошибки с литералом объекта могут указывать только известные свойства, а 'a' не существует в типе 'Partial'options.input
ошибок с Свойство "input" не существует для типа "O".out.b
(иout.c
) ошибок с свойством 'b' не существует для типа 'Partial'.
1 ответ
Я на самом деле не эксперт в этом вопросе, но, поиграв некоторое время с вашим запросом, я понял несколько вещей:
- TypeScript может различать только типы объединения; он не может различать дженерики.
- Указанная дискриминация не распространяется вверх; то есть TypeScript не будет автоматически различать родительский объект на основе распознавания дочерних объектов.
Как говорится, вот решение, которое я придумал:
type Base = { // I'm assuming this is the base type for Foo and Bar
a: string;
}
interface Foo extends Base { // so I shall modify here a bit
b: string;
}
interface Bar extends Base { // and here of course
c: string;
}
interface Scenarios {
// Now I put the return type inside the option, to make the discrimination work
// Of course, I need to make 'ret' optional, or your input won't make sense
foo: { kind: 'foo', input: string, ret?: Foo },
bar: { kind: 'bar', ret?: Bar },
}
// Notice the use of 'Required' here; that brings the actual type of 'ret' back
function test<X extends keyof Scenarios>(options: Scenarios[X]): Required<Scenarios[X]>['ret'] {
let data = options as Scenarios[keyof Scenarios]; // create a union type
data.ret = { a: 'one' } as Scenarios[keyof Scenarios]['ret']; // type assertion
if (data.kind === 'foo') {
data.ret!.b = data.input; // finally the discrimination works!
}
if (data.kind === 'bar') {
data.ret!.c = "whatever"
}
return data.ret!;
}
Хорошо, пока все хорошо, к сожалению, я все еще не могу заставить TypeScript автоматически выводить общий аргумент. Скажи, если я побегу:
var result = test({ kind: 'foo', input: "aas" }); // oops
Тогда TypeScript все еще не может понять, что result
имеет тип Foo
. Но, конечно, такого рода автоматический вывод имеет очень низкую практическую ценность, поскольку даже если он и работает, он работает только тогда, когда вы буквально вводите слово «foo» в аргументе вашей функции, и если вы можете это сделать, что мешает вам ввести общий аргумент?
You may try these code in this Ссылка на игровую площадку
Обновить:
Я только что нашел решение моей последней проблемы:
function test<X extends keyof Scenarios>(options: { kind: X } & Scenarios[X]): Required<Scenarios[X]>['ret'] {
....
}
var result = test({ kind: 'foo', input: "aas" }); // It works!
Хитрость заключается в том, чтобы добавить { kind: X }
к объявлению параметра.
See this Ссылка на игровую площадку.
Похожие вопросы
Новые вопросы
typescript
TypeScript — это типизированный надмножество JavaScript, транспилируемое в обычный JavaScript. Он добавляет в JavaScript необязательные типы, классы, интерфейсы и модули. Этот тег предназначен для вопросов, специфичных для TypeScript. Он не используется для общих вопросов по JavaScript.