正如microsoft/TypeScript#12936中所要求的,TypeScrip并不直接支持所谓的"精确类型".TypeScrip中的所有对象类型都被认为是"开放的"或"可扩展的",因此一个值具有比类型中已知的属性更多的属性总是可能的.类型{a: string}
的值仅已知在键"a"
处拥有string
值的属性.例如,密钥"b"
处的属性not对于lack是已知的.因此,{a: string}["b"]
的类型更类似于unknown
(它可能是任何东西),而不是undefined
(已知它不存在).即使是像这样的对象文字
const o = { foo: 'val1', bar: 'val2' } as const;
/* const o: {
readonly foo: "val1";
readonly bar: "val2";
} */
具有这样的技术可扩展类型:
const p = { foo: 'val1', bar: 'val2', baz: 'val3' } as const;
const q: typeof o = p; // okay
甚至没有一种方法可以在类型系统中一致地表示这样的类型.您可以得到的最接近的结果是,给该类型一个index signature来描述任意关键帧的属性.但是,您不能使用索引签名来为已知键设置例外.也就是说,以下类型无效:
interface Nope {
readonly foo: "val1";
readonly bar: "val2";
readonly [rest: string]: undefined;
}
这里的索引签名意味着具有string
键的every属性将具有undefined
值.这与"foo"
和"bar"
属性冲突,编译器会抱怨.在microsoft/TypeScript#17867处有一个长期开放的功能请求,该请求允许对已知键进行例外的"REST"索引签名.但现在,它不是语言的一部分.
因此,您可以编写的最接近的有效类型是
interface Yep {
readonly foo: "val1";
readonly bar: "val2";
readonly [all: string]: "val1" | "val2" | undefined;
}
这对于您的用例来说可能已经足够了:
declare const key: string;
const myObject: Yep = { foo: 'val1', bar: 'val2' };
const r = myObject.foo; // const r: "val1"
const s = myObject[key]; // const s: "val1" | "val2" | undefined
const t = myObject.baz; // const t: "val1" | "val2" | undefined 🤷♂️
如果它足够好,您可以编写一个帮助器函数来自动为对象提供这样的类型:
const exactish = <const T,>(
x: T & { [all: string]: T[keyof T] | undefined }
) => x
其中,intersection将输入类型T
与适当属性类型的索引签名组合在一起,并且我使用const
type parameter来消除调用者使用const
断言的需要:
const myObject = exactish({
foo: 'val1',
bar: 'val2'
});
/* const myObject: {
foo: "val1";
bar: "val2";
} & {
[all: string]: "val1" | "val2" | undefined;
} */
这是一个等价于Yep
以上的类型,因此无论好坏,它的行为都是相同的:
const r = myObject.foo; // const r: "val1"
const s = myObject[key]; // const s: "val1" | "val2" | undefined
const t = myObject.baz; // const t: "val1" | "val2" | undefined
Playground link to code个