这是一个简化的代码片段,可以重现我的问题:
type Supported = 'foo'|'bar';
type State = {
[K in Supported]?: K[]
}
function test<T extends keyof State>(state: State, type: T) {
const arr = state[type];
if (!arr) {
return;
}
return arr[0];
}
function test2<T extends keyof State>(state: State, type: T) {
const arr = state[type] as T[]|undefined;
if (!arr) {
return;
}
return arr[0];
}
在第一个函数中,arr的类型是if块之前的State [T]。变成“ foo” [] |之后是“ bar” []。 在test2中,我将arr手动转换为状态的实际值类型,并且返回值类型正确。
在类型保护过滤出未定义的值之后,通用类型T似乎丢失了。这是预期的行为吗?
答案 0 :(得分:1)
实际上,您的第一个示例是正确推断的,第二个示例中的类型转换和数组类型有点不准确。在您的具体情况下,这无关紧要,因为您只返回第一个元素。首先,让我们看一下test
:
test
功能 State[T]
与"foo"[] | "bar"[] | undefined
相同。您也可以这样写:
State[T] -> State["foo" | "bar"] -> State["foo"] | State["bar"] -> "foo"[] | "bar"[]
=> "foo"[] | "bar"[] | undefined (optional properties possible)
因此,arr
在函数末尾的类型为"foo"[] | "bar"[]
,arr[0]
类型为"foo" | "bar"
的类型是正确的,因为if块排除了未定义的值。 IntelliSense显示的类型表示可能会有些混乱,因为有时它们最终会变得更加冗长/细粒度,有时会变得更加紧凑/无法解析。编译器的规范类型相同。
test2
的比较在一开始,我说过您的强制转换数组类型有点不准确。假设我们返回test1
和test2
中的整个数组(不仅是第一个元素),以说明问题。
test
和test2
的新功能签名
// test signature
<T extends Supported>(state: State, type: T): State[T]
// test2 signature
<T extends Supported>(state: State, type: T): T[] | undefined
测试用例:
// define some variables
declare const state: State;
declare const stateType: "foo" | "bar";
// invoke functions
test(state, stateType); // return type: "foo"[] | "bar"[] | undefined
test2(state, stateType); // return type: Supported[] | undefined
结果:
const test_sample1: "foo"[] | "bar"[] | undefined = ["foo", "foo"] // works
const test_sample2: "foo"[] | "bar"[] | undefined = ["foo", "bar"] // <-- error!
const test2_sample1: Supported[] | undefined = ["foo", "bar"] // works
const test2_sample2: Supported[] | undefined = ["foo", "foo"] // works
因此,在test2
中进行手动投射时,您可以返回["foo", "bar"]
,而test
是不可能的。部分原因是,以下内容不同:
"foo"[] | "bar"[] !== ("foo"|"bar")[]
according to the documentation
希望,它会有所帮助。干杯
答案 1 :(得分:1)
我认为这里发生的事情类似于this reported issue和this Stack Overflow question ...当您从constrained generic中读取属性时(例如State[K]
,其中{{1 }}),泛型类型被扩展到其约束(因此K extends keyof State
变成K
,并且keyof State
被评估为State[K]
,即State[keyof State]
。您的情况并非完全是错误,"foo"[] | "bar"[] | undefined
的类型为state[type]
的事实……这并不是您所特有的 d喜欢。
您想将"foo"[] | "bar"[] | undefined
看作是state[type]
之类的东西。但是编译器只是不为您执行此操作……它需要进行一些高级类型分析,而编译器不知道该如何执行。它必须能够计算K[] | undefined
等效于Exclude<State[K], undefined>[0]
,而不能。
在K
中使用的类型断言对我来说似乎是一个合理的解决方法。
另一种可能性是将test2
参数的类型从state
扩展到在索引时被强制视为State
的值。例如,类似K[] | undefined
的东西。直接使用该参数的唯一问题是,编译器将同时使用Partial<Record<K, K[]>>
和type
参数来推断state
,并且可能会将K
扩展到完整的{{ 1}}。我们只想使用K
来推断keyof State
,所以最好告诉编译器使用type
in a "non-inferential" way中的类型参数。一种实现此目的的方法according to a language maintainer是在我们不希望进行推理的地方用K
替换state
。这导致我们想到这一点:
K
现在,编译器可以理解,在消除K & {}
之后,function test3<K extends keyof State>(
state: Partial<Record<K & {}, K[]>>,
type: K
) {
const arr = state[type];
// const arr: { [P in K]?: P[] | undefined; }[K]
if (!arr) {
return;
}
// const arr: K[]
return arr[0];
}
将是undefined
类型。因此,arr
的返回类型被推断为K[]
。万岁!是的,与您的类型断言相比,这要花很多时间,因此实际上,我可能只是断言并继续前进。
希望有所帮助;祝你好运!