У меня проблема с присваиваемыми значениями в одной из моих общих функций:
interface BuildArguments<T extends string> {
type: T;
}
type PromiseResult<T> =
T extends 'standalone' ? Promise<void> :
T extends 'all' ? Promise<void> :
Promise<void[]>;
const foo: PromiseResult<'standalone'> = Promise.resolve();
const bar: PromiseResult<'all'> = Promise.resolve();
const baz: PromiseResult<'foo'> = Promise.resolve([]);
bundle({ type: 'foo' });
function bundle<T extends string>(buildArguments: BuildArguments<T>): PromiseResult<T> {
switch (buildArguments.type) {
case 'standalone':
return Promise.resolve(); // error here, not assignable to PromiseResult<T>
case 'all':
return Promise.resolve(); // error here, not assignable to PromiseResult<T>
default:
return Promise.all([ // error here, not assignable to PromiseResult<T>
Promise.resolve(),
Promise.resolve()
]);
}
}
const
s foo
, bar
и baz
показывают, что условный тип работает нормально. вызов функции bundle({ type: 'foo' })
также правильно предоставляет тип Promise<void[]>
, если вы используете игровую площадку ts и наводите на нее курсор. Почему это не работает для возвращаемых значений?
Я также пробовал, если это вызвано тем, что TypeScript не может вывести T
, добавив аргумент kind: T
к функции, но без изменений. Утверждение Promise.resolve()
на PromiseResult<T>
работает нормально.
Typescript, как правило, не позволит вам много делать с условными типами, пока они все еще имеют неразрешенные условные типы (как T
в случае PromiseResult<T>
)
Более того, в этом случае вы предполагаете, что сужение buildArguments.type
сужает T
. Это не. Сужение сужает значение, а не весь параметр типа. По-другому и быть не может, рассмотрим этот пример:
function foo<T extends string | number>(a: T, b: T) {
if (typeof a === "string") {
// should b be string? No
}
}
foo<string | number>(1, "");
То, что мы сузили одно значение типа T
, не имеет отношения к другим таким переменным.
Самое простое решение — использовать отдельную сигнатуру реализации, которая является более разрешительной, сохраняя при этом общедоступную сигнатуру с условными типами, которые лучше подходят для вызывающей стороны:
function bundle<T extends string>(buildArguments: BuildArguments<T>): PromiseResult<T>
function bundle(buildArguments: BuildArguments<string>): PromiseResult<'standalone' | 'all'> | PromiseResult<string> {
switch (buildArguments.type) {
case 'standalone':
return Promise.resolve();
case 'all':
return Promise.resolve();
default:
return Promise.all([
Promise.resolve(),
Promise.resolve()
]);
}
}