如果我按如下方式定义我的类型,它会遵循我想要的行为。
interface Foo {}
interface Bar {
a: string;
b: boolean;
c: Foo;
}
type x = keyof Bar; // "a" | "b" | "c"
但是,如果我尝试添加索引签名,它将丢失所有预定义成员。
interface Bar {
[index: string]: any;
}
type x = keyof Bar; // string | number
有没有办法在 TypeScript 中正确执行此操作?
类似于:
type x = Exclude<Bar, { [index: string]: any }>; // never
编辑我尝试了类似于杰克的解决方案并得到了这个:
interface Indexable<T> {
[index: string]: any;
}
type BaseType<T> = T extends Indexable<infer U> ? U : never;
interface BaseFoo {
Name: string;
}
interface Foo1 extends Indexable<BaseFoo> {}
type Foo2 = Indexable<BaseFoo>;
type base1 = BaseType<Foo1>; // {}
type base2 = BaseType<Foo2>; // BaseFoo
Foo1
不起作用,由于某种原因,其类型信息变为{}
.Foo2
确实有效,但智能感知不会说Foo2
对于Foo2
类型的变量。相反,他们有Indexable<BaseFoo>
.
我真的很想尝试对我的用户隐藏这种类型的按摩。不幸的是,要求他们从Indexable<T>
到T
来回投射是不可行的.
答案
更新:从TS 4.1开始,有一个真正的解决方案:如何使用映射类型删除索引签名
另类
在添加索引签名之前获取密钥:
interface Foo {}
interface BarCore {
a: string;
b: boolean;
c: Foo;
}
type Bar = BarCore & {
[index: string]: any;
}
type X = keyof BarCore; // a|b|c
更多
PS:尽量不要将索引签名与根级别的有效 prop 混合使用。改用嵌套对象模式
不,因为这是正确的行为。string | "x"
将简化为string
"x" extends string
因为这是真的。当您只定义string
索引签名时,您会string | number
,因为 JavaScript 将数字索引强制转换为对象上的字符串索引。
如果你想要你要找的行为,你需要改变你的接口定义。
interface Foo {}
interface Bar {
a: string;
b: boolean;
c: Foo;
}
interface IndexedBar extends Bar {
[ key: string ]: any;
}
type x = keyof Bar; // "a" | "b" | "c"
另请注意,在某些情况下,您不会对IndexedBar
进行正确的类型检查。
function setValue(obj: IndexedBar, key: string, value: any): void {
obj[key] = value;
}
setValue(bar, "a", 4); // No error, even though a is explicitly a string.