我们有一个泛型类型,它成功地从它接收的类型中提取数据属性。它是in the playground,它是这样工作的。
type DataPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type FooBarBaz = {
foo: string;
bar: Function;
baz: number;
};
const f1 = () => {
type DataProps = DataPropertyNames<FooBarBaz>;
const x: DataProps = 'foo'; // good, no error, because foo is a string
const y: DataProps = 'bar'; // good, expected error, because bar is a Function.
return {
x,
y
};
};
上面的例子展示了当我们向DataPropertyNames
传递一个具体的类型,比如FooBarBaz
时,它是如何工作的。正如预期的那样,它丢弃了属于函数的道具,并保留了非函数的道具。
我们的问题是,为什么下面的代码不能像预期的那样工作。我们预期DataProps
会接受'foo'
字符串,因为T
扩展了FooBarBaz
。
const f2 = <T extends FooBarBaz>() => {
type DataProps = DataPropertyNames<T>;
const x: DataProps = 'foo'; // bad, unexpected error
const y: DataProps = 'bar';
return {
x,
y
};
};
为什么TypeScript不能确定T
至少与它扩展的类型具有相同的数据属性名称?我们如何才能让TypeScript相信这一点呢?
发布于 2020-09-18 02:06:56
目前,TypeScript还没有成熟到可以通过复杂的定义解析泛型参数,并通过参数的约束来简化它;即使它可以,在您的例子中,结果也应该是类似unknown | 'foo' | 'baz'
的东西,其中unknown
是不可避免的,因为我们真的不知道T
中有什么,这将简单地简化为unknown
,这并不是真正有帮助的。
但对于您的特定情况,一种使其工作的方法是使用以下代码:
type DataProps = DataPropertyNames<T> | DataPropertyNames<FooBarBaz>;
除非您的类型约束是另一个泛型参数,否则这应该可以满足您的需要。
发布于 2020-09-18 14:03:22
Mu-Tsan Tsai的答案是正确的,我只是想为了更清楚地说明简单的泛型类型是有效的。我一直对这种情况很好奇,我认为提供一个实验,让我们可以看到什么时候类型变得太复杂是很有用的。你可以使用下面的例子(通过playground链接),看看typescript的未来版本是否有所改善(我的实验是在TS4.0.2中运行的)。
在下面的代码中,当泛型T直接与扩展约束一起使用时,我们可以访问泛型约束中使用的类的属性。这是好的,也是意料之中的,这并不奇怪。当你有一个非常简单的带有T的泛型类型(basicGenType),它也可以工作,在我的例子中,我使用了keyof运算符,泛型类型能够从T中提取键。实际上,我对此感到有点惊讶,因为我认为当使用T作为泛型参数时,任何泛型类型都会失败。对于稍微复杂一点的基本类型(basicGenType2,basicGenType3),它会失败。似乎有一次我开始推断一个新的类型(在本例中是K),并用它来组合结果类型,这就是断点。一个有趣的观察是,智能感知会自动完成basicGenType2和basicGenType3的关键字段。
一旦我测试了扩展并推断它也失败了,这并不令人惊讶,它符合你的经验。
type basic = {simple:number}
type basicGenType<T> = keyof T
type basicGenType2<T> = {[K in keyof T]: K}
type basicGenType3<T> = {[K in keyof T]: T[K]}
type complexGenType1<T> = T extends basic ? number : never
type complexGenType2<T> = T extends {simple:infer R} ? R : never
const g = <T extends basic>(a:T):void => {
//directly on T typescript can use the properties of basic
a.simple = 5; //GOOD --typescript is good with this
a.simple = "5" //GOOD -- typescript complains
a.other = 5; //GOOD -- typescript complains
//for a simple generic type typescript still works
//the resulting type should only allow the value 'simple'
type basicType = basicGenType<T>
let b:basicType = 'simple'; //GOOD -- typescript is ok with this
b = 'other'; //GOOD -- typescript complains
//for a slightly more complicated simple type we fail
// the resulting type should only allow the value {simple:'simple'}
type basicType2 = basicGenType2<T>
let b2:basicType2 = {simple:'simple'}; //BAD -- typescript fails even though it should be ok with this
//for a slightly more complicated simple type we fail
// the resulting type should only allow the value {simple:number}
type basicType3 = basicGenType3<T>
let b3:basicType3 = {simple:3}; //BAD -- typescript fails even though it should be ok with this
//we've introduced extends here for complexity
//complexType shoud resolve to number here
type complexType1 = complexGenType1<T>;
let c:complexType1 = 5;//BAD -- typescript fails even though it should be ok with this
//we've introduced extends and infer here for even more complexity
//complexType2 shoud resolve to number here
type complexType2 = complexGenType2<T>;
let d:complexType2 = 5;//BAD -- typescript fails even though it should be ok with this
}
https://stackoverflow.com/questions/63948187
复制