请考虑以下代码:

const myObject = {
  foo: 'val1',
  bar: 'val2'
} as const

type keys = keyof typeof myObject // 'foo' | 'bar'

const key: string = getKey() // returns some random string key

const result: 'val1' | 'val2' | undefined = myObject[key]

TypeScrip在myObject[key]处抛出错误,并显示消息Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ readonly foo: "val1"; readonly bar: "val2"; }'. 这是真的,但我并不介意-如果key既不是foo也不是bar,那么我可以接受计算结果为undefined的表达式.

我希望类型result'val1' | 'val2' | undefined,这是一个完全有效的--不需要断言.

有没有办法强迫TS做这件事?

推荐答案

正如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

Typescript相关问答推荐

为什么在TypScript中写入typeof someArray[number]有效?

为什么ESLint抱怨通用对象类型?

typescript抱怨值可以是未定义的,尽管类型没有定义

TypeScrip:基于参数的条件返回类型

TypeScrip表示该类型不可赋值

如何以Angular 形式显示验证错误消息

如何使默认导出与CommonJS兼容

Cypress页面对象模型模式.扩展Elements属性

Fp-ts使用任务的最佳方法包装选项

FatalError:Error TS6046:';--模块分辨率';选项的参数必须是:'; node ';,'; node 16';,'; node

如何正确调用创建的类型?

如何从对象或类动态生成类型

推断集合访问器类型

作为参数的KeyOf变量的类型

两个名称不同的相同打字界面-如何使其干燥/避免重复?

为什么看起来相似的代码在打字时会产生不同的错误?

类型分布在第一个泛型上,但不分布在第二个泛型上

如何将 MUI 主题对象的自定义属性与情感样式组件中的自定义props 一起使用?

为什么我的 Typescript 函数中缺少 void 隐式返回类型?

基于泛型的柯里化函数的条件参数