我试图在TypeScript中重写一个对象,其中值的类型取决于键.我希望TypeScript根据循环中的键缩小值的类型.以下是我的初始代码:

interface MyInterface {
  name: string;
  age: number;
  hobbies?: string[];
}

const obj: MyInterface = {
  name: 'John',
  age: 23,
};

for (const key in obj) {
  const value = obj[key];
  // TypeScript treats key as string, and value as any
  // I want it to treat value as string when key is 'name', number when key is 'age', etc.
}

for...in循环中,TypeScript将key视为string,将value视为any.然而,我希望它在key等于'name'时将value视为string,当key等于'age'时将number视为key,当key等于'hobbies'时将string[]视为key.

我试图通过创建一个笨拙的类型生成器函数来解决这个问题,该函数生成具有类型的键值对:

function* iterateObjectTyped<T extends MyInterface, K extends keyof T>(
  obj: T
): Generator<[K, T[K]]> {
  for (const key in obj) {
    const value = obj[key as unknown as K];
    yield [key as unknown as K, value];
  }
}

for (const [key, value] of iterateObjectTyped(obj)) {
  // now TypeScript treats key as "name" | "age" | "hobbies"
  // and value as string | number | string[]
  // however:
  if (key === 'age') {
    value;
    // it still treats value as just string | number | string[]
    // even though I expected the above check to narrow down the predictions
  }
}

但这并不像预期的那样工作.尽管TypeScript现在在循环中将key视为"name" | "age" | "hobbies",将value视为string | number | string[],但它并没有根据if判断中的key来缩小value的类型.

有没有一种方法可以在TypeScript中实现这一点?使用TypeScript 5.4.3.任何帮助将不胜感激.

推荐答案

TypeScript中的对象类型是open/extendable/"inexact",而不是closed/sealed/"exact"(其中"inexact"/"exact"是Flow的术语).这意味着一个对象被允许包含比TypeScript知道的更多的属性:

const obj2 = { name: "John", age: 23, a: true, b: null };
const obj3: MyInterface = obj2; // okay
for (const key in obj3) {
  const value = obj[key];
        // ^? string
}

这里obj3具有属性ab,即使它是类型MyInterface.所以TypeScript可以说key是类型string,因此不是已知的密钥MyInterface,因此value是类型any.

TypeScript: Object.keys return string[].

如果你愿意,你可以assert个对象只有TypeScript知道的键,这很好.只要记住,如果你错了,什么东西坏了,你要负责.


无论如何,假设你想给你的iterateObjectTyped生成器一个调用签名,使得返回输出的每个元素都是[K, T[K]]个元素对的条目,对于keyof T中的K个元素,你可以这样写:

function* iterateObjectTyped<T extends object>(
  obj: T
): Generator<{ [K in keyof T]-?: [K, T[K]] }[keyof T]> {
  for (const key in obj) {
    const value = obj[key];
    yield [key, value];
  }
}

类型{ [K in keyof T]-?: [K, T[K]] }[keyof T]是所谓的distributive object type,如microsoft/TypeScript#47109所创造.它从T迭代每个键K,产生[K, T[K]],然后得到union个键.

如果我们打iterateObjectTyped(obj),你会看到:

// function iterateObjectTyped<MyInterface>(obj: MyInterface): Generator<
//   ["name", string] | ["age", number] | ["hobbies", string[] | undefined]
// >

这是一个对的并,由于每对的第一个元素是literal type,所以它作为discriminated union的判别性质:

for (const [key, value] of iterateObjectTyped(obj)) {
  if (key === 'age') {
    value; // const value: number
  }
}

这是预期的,因为支持destructuring of discriminated unions into separate variables.因此,判断key === 'age'将缩小valuenumber.

Playground link to code

Typescript相关问答推荐

如何使用TypScript和react-hook-form在Next.js 14中创建通用表单组件?

如何使类型只能有来自另一个类型的键,但键可以有一个新值,新键应该抛出一个错误

如何判断输入是否是TypeScript中的品牌类型?

ANGLE找不到辅助‘路由出口’的路由路径

缩小并集子键后重新构造类型

使用2个派生类作为WeakMap的键

对于始终仅在不是对象属性时才抛出的函数,返回Never

`fetcher.load`是否等同于Get Submit?

Angular:ngx-echart 5.2.2与Angular 9集成错误:NG 8002:无法绑定到选项,因为它不是div的已知属性'

如何从MAT-FORM-FIELD-MAT-SELECT中移除焦点?

Typescript 类型守卫一个类泛型?

有没有办法防止类型交集绕过联合类型限制?

跟踪深度路径时按条件提取嵌套类型

如何静态键入返回数组1到n的函数

类型';只读<;部分<;字符串|记录<;字符串、字符串|未定义&>';上不存在TS 2339错误属性.属性&q;x&q;不存在字符串

窄SomeType与SomeType[]

如何提取具有索引签名的类型中定义的键

我应该使用服务方法(依赖于可观察的)在组件中进行计算吗?

将对象数组传递给 Typescript 中的导入函数

使用受保护的路由和入门来响应路由