考虑以下代码
type Foo = {
kind: "foo";
foo: number;
};
type Bar = {
kind: "bar";
bar: number;
};
type FooBar = Foo | Bar;
function foobar(arg: FooBar, kind: "foo" | "bar"): FooBar | undefined {
if (arg.kind === kind) {
return arg;
}
return undefined;
}
declare const a: FooBar;
if (a.kind === "foo") {
a.foo; // no error, type is correctly narrowed
}
foobar(a, "foo").foo; // error: Property 'foo' does not exist on type 'Bar'
是否可以为foobar
函数编写类型签名,因此根据kind
参数缩小返回值?
答案 0 :(得分:3)
我们可以通过使用通用参数对要在输出类型中用作“变量”的类型进行建模来提取类型。
在这种情况下,我使用ExtractKind<T>
提取不同类型T的并集。
然后我们可以使用RefineKind<T, K>
-其中T是带有种类的类型,而K是要精炼到的类型。
我用未定义的检查包装了输出结果-如果您愿意,我可以提供一个示例,说明如何详尽地映射所有类型以消除对此检查的需要,但这似乎超出了原始问题的范围。
如果您需要进一步解释这些概念,请告诉我!
完整示例:
type Foo = {
kind: "foo";
foo: number;
};
type Bar = {
kind: "bar";
bar: number;
};
type FooBar = Foo | Bar;
type ExtractKind<T> = T extends { kind: infer K } ? K : never;
type RefineKind<T, K> = T extends { kind: K } ? T : never;
function foobar<K extends ExtractKind<FooBar>>(arg: FooBar, kind: K) {
if (arg.kind === kind) {
return arg as RefineKind<FooBar, K>;
}
return undefined;
}
const foo = foobar(a, "foo");
if (foo !== undefined) {
foo.foo; // foo -> number
}
打字机游乐场链接here
答案 1 :(得分:1)
我认为您实际上要在这里实现user-defined type guard。本质上,您编写了一个返回类型为arg is SomeType
的布尔函数,它告诉打字稿该函数是否为true
而不是arg
的类型必须为Sometype
。而且如果它是false
,则意味着arg
的类型不能为Sometype
。
这是您的情况下的功能:
function isKind<T extends Kinds>(arg: FooBar, kind: T): arg is Extract<FooBar, {kind: T}> {
return arg.kind === kind;
}
在if
语句中使用此函数时,您会获得有关该类型的有意义的信息:
function testUnknown( either: FooBar ) {
if ( isKind(either, "bar") ) {
// we now know that either is Bar
const barVal = either.bar; // ok
const fooVal = either.foo; // error
}
else {
// we now know that either is Foo
const barVal = either.bar; // error
const fooVal = either.foo; // ok
}
}
我个人更愿意将isKind
作为双箭头函数编写,以便您可以更轻松地在array.map或其他回调中调用isKind("foo")
,但这取决于代码样式和个人喜好
const isKind = <T extends Kinds>(kind: T) =>
(arg: FooBar): arg is Extract<FooBar, {kind: T}> => {
return arg.kind === kind;
}
您原来的foobar
函数可以在内部使用isKind
类型防护。这类似于@mbdavis的答案,但是您不需要使用arg
来断言"as"
的类型,因为打字稿已经知道类型已经精炼了。
function foobar<T extends Kinds>(arg: FooBar, kind: T): Extract<FooBar, {kind: T}> | undefined {
return isKind(arg, kind) ? arg : undefined;
}
但是我怀疑您甚至根本不需要此foobar
函数,而可以直接在您的代码中调用isKind
。