如何以正确的类型安全方式迭代记录键?

2024-02-10

让我们针对一些属性构建一个示例记录。

type HumanProp = 
    | "weight"
    | "height"
    | "age"

type Human = Record<HumanProp, number>;

const alice: Human = {
    age: 31,
    height: 176,
    weight: 47
};

对于每个属性,我还想添加一个人类可读的标签:

const humanPropLabels: Readonly<Record<HumanProp, string>> = {
    weight: "Weight (kg)",
    height: "Height (cm)",
    age: "Age (full years)"
};

现在,使用此记录类型和定义的标签,我想迭代具有相同键类型的两个记录。

function describe(human: Human): string {
    let lines: string[] = [];
    for (const key in human) {
        lines.push(`${humanPropLabels[key]}: ${human[key]}`);
    }
    return lines.join("\n");
}

但是,我收到错误:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Readonly<Record<HumanProp, string>>'.
  No index signature with a parameter of type 'string' was found on type 'Readonly<Record<HumanProp, string>>'.

如何在 Typescript 中正确实现此功能?

为了澄清,我正在寻找的解决方案,无论它是否会使用Record类型、普通对象、类型、类、接口或其他东西,应该具有以下属性:

  1. 当我想定义一个新的属性时,我只需要在一个地方完成(就像上面的 HumanProp 一样),并且不要重复自己。

  2. 定义新属性后,我应该为该属性添加新值的所有位置,就像我创建时一样alice, or humanPropLabels, 点亮类型错误在编译时,而不是在运行时错误。

  3. 迭代所有属性的代码,例如describe函数,当我创建新属性时应保持不变。

是否有可能使用 Typescript 的类型系统来实现类似的东西?


我认为正确的方法是创建一个不可变的键名数组并给它一个窄类型,以便编译器将其识别为包含字符串文字类型 https://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types而不是仅仅string。这是最简单的const断言 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions:

const humanProps = ["weight", "height", "age"] as const;
// const humanProps: readonly ["weight", "height", "age"]

然后你可以定义HumanProp就其而言:

type HumanProp = typeof humanProps[number];

其余代码应该或多或少按原样工作,除了当您迭代键时,您应该使用上面的不可变数组而不是Object.keys():

type Human = Record<HumanProp, number>;

const alice: Human = {
   age: 31,
   height: 176,
   weight: 47
};

const humanPropLabels: Readonly<Record<HumanProp, string>> = {
   weight: "Weight (kg)",
   height: "Height (cm)",
   age: "Age (full years)"
};

function describe(human: Human): string {
    let lines: string[] = [];
    for (const key of humanProps) { // <-- iterate this way
        lines.push(`${humanPropLabels[key]}: ${human[key]}`);
    }
    return lines.join("\n");
}

不使用的原因Object.keys()是编译器无法验证类型的对象Human will only已在中声明密钥Human。 TypeScript 中的对象类型是开放的/可扩展的,而不是封闭的/exact https://github.com/microsoft/TypeScript/issues/12936。这允许接口扩展和类继承工作:

interface SuperHero extends Human {
   powers: string[];
}
declare const captainStupendous: SuperHero;
describe(captainStupendous); // works, a SuperHero is a Human

你不会想要describe()爆炸,因为你传入了SuperHero,一种特殊类型的Human额外powers财产。所以不要使用Object.keys()哪个正确地产生string[],您应该使用已知属性的硬编码列表,以便代码像describe()将忽略任何额外的属性(如果存在)。


并且,如果您添加一个元素humanProps,你会在你想要的地方看到错误describe()将保持不变:

const humanProps = ["weight", "height", "age", "shoeSize"] as const; // added prop

const alice: Human = { // error! 
   age: 31,
   height: 176,
   weight: 47
};

const humanPropLabels: Readonly<Record<HumanProp, string>> = { // error!
   weight: "Weight (kg)",
   height: "Height (cm)",
   age: "Age (full years)"
};

function describe(human: Human): string { // okay
   let lines: string[] = [];
   for (const key of humanProps) {
      lines.push(`${humanPropLabels[key]}: ${human[key]}`);
   }
   return lines.join("\n");
}

好的,希望有帮助;祝你好运!

Playground 代码链接 https://www.typescriptlang.org/play/#code/HYQwtgpgzgDiDGEAEB5ANgEyQbwFBIPgHtgoAXJACwFcwRgAFAJyJiiQF4kBtAIgHcIASwDmlMrwA0SXpWFiJ03iBEReAXSQh2xUmQDc+AmQCeMZAAla9Zq05JT5ogDMq1xizbdgtAEYQmdUMCBzNLd3sAJQhiJgwAHis6D1ZpHzB-JgA+QyMkXXItNCFEAC4kJPp7PBCQlQhygGYARkk8kLlRcXLmgHYANjbagkEusnKAFl68gF99JFw8gooaZNsYABkQfzQocuiQDBI0E3jo2ITKlJhpciYhYBEsrOr2kflumQB1D4oACgA1iIAJRSN5UX7lXgWX5IP7wMCgobDepQgCCqjhzmoaDQSBMEBATCgoNmuRC2OA8DIQhISAw0Hg938f1W9HKV2B5TuDxEOHBaAgFGKwGg3LI90e3E0XGlwWGziITDhyyQAIgJiQLjca08JP5w1qIugADoYNQoJQ-gADAAk2DZ1y2Oyg3HVJnUM3K9sdbo1nutwPltRm4KYQuoTGASGNUBNACsiA8-rwADrAXhB2aLEIPMgBZwIZAAZWo5iYMJYSAgAA988AMOwrgbhjAiIJieLJSI5dmQgz4GgichVfAQDAyCAHsWyGWIA2iBbyqXy5WiMGGVAmUIWWOJ1PgDO5wuLUGkAB6c9IfhKgFQaQgJArgJrpBCdiPpIkXCh3CgSCwEWSAAHIQPwLb5CQhSOus7CygIvxSDInQKEhyiqGhlpEBAxZCAAXmomjaJBejBo44S6nYXDkdqMF6t4fgBEEeTkRUERcOcSqXO46xpIx2TkoQUEUCAxRlGxyTVBeV4BCwTAAITgqiSAtMitQoZ8fSDOCowKJM0whHMOZCXoOo2J4zoQLs+yEkcwAnGcMRcYkPGeLcEq8s8UmXtWTByYphq6Z8vA-GMcJAki4IaeMMgwmF8KImChrKbwGLIH82K4vihLEqShmCUglLUrS0abtuLKOhy7hckgPKPBBISCsKDxirVHlSjKPDMYairKvCwlqhqWquHRrD6jUhoELGZoWladoOq5rCWbsfoel6SA+u4q0Blmhqhoa4azlGMYtXGibJmmGa7QQoa-kAA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何以正确的类型安全方式迭代记录键? 的相关文章

  • React 16.7 - React.SFC 现已弃用

    我用来声明无状态组件 如下所示 const example React SFC
  • 打字稿构建失败

    我目前有两个使用 typescript 1 1 的项目 它们在安装了 typescript 1 8 的 Visual Studio 2013 中打开 VS 要求升级 我这样做了 但现在出现以下错误 未知的编译器选项 listemitedfi
  • 正则表达式仅允许少数特殊字符以及 az 或 AZ

    有一个接受 a zA Z 的字段 我使用 regEx 来正常工作 a zA Z s 现在字段还应该排除除 之外的所有字符 我已经修改了正则表达式以支持此更改 新的正则表达式是 a zA Z s 但看起来它不起作用 有人可以告诉我我做错了什么
  • Rxjs 带延迟功能的重试

    我正在尝试使用retry with delay函数 我预计函数会在 1000 毫秒延迟后调用 但事实并非如此 这里可能会出现什么错误 查看控制台输出 同一时间 16 22 48 我预计有 16 22 48 16 22 59 canCreat
  • Typescript 中的子类化

    我在 Typescript 中有一个存储类 它实现了Storage界面 说MyStorage 但它的方法太多了 所以我想用它来代替MyStorage getCandy MyStorage Candies getCandies 该结构可能看起
  • TypeScript问题如何在Chart.js和react-chartjs-2的自定义插件中传递参数

    我创建了一个自定义插件horizontalArbitraryLine import Plugin from chart js export const horizontalArbitraryLine Plugin id horizontal
  • 从联合类型映射多个兼容类型

    我正在开发一个应用程序 我们已经定义了一个类型 并且需要从该单个接口中推导出多个接口 Example 我们的主要类型看起来像这样 该类型是否定义为映射类型或联合类型并不重要 因此最简单的解决方案都是最好的 type A type A inp
  • Angular 12.1 使用 typescript 添加 html 元素

    我正在通过 youtube 学习 Angular 但我正在尝试做一些新的事情 但我遇到了一个错误 我的代码附在下面 请帮助我 我想这样设置属性div setAttribute click popUp event 但我得到了错误 打字稿 ex
  • 如何在 Angular 2 中处理 200 以外的 http 状态码

    现在我做http请求的方式 借用这个答案 https stackoverflow com a 34758630 4937981 这是 POST url data var headers new Headers authtoken local
  • 如何在 TypeScript 中获取类方法的返回类型

    在较新的 TypeScript 版本中 我想是 2 8 以后 我可以轻松获取函数的返回类型 function f return hi type MyType ReturnType
  • 如何为 Angular 4、5 中的动态元素分配哈希 ID ref id

    如果我的问题很愚蠢 我很抱歉 我有 30 多个静态 ng container 和一些独特的静态 hashtagID 但我想在 ngFor 的帮助下动态创建它们 public lists food book cook 期望输出
  • 如何在 Typescript 中使用默认值定义可选构造函数参数

    是否可以有带有默认值的可选构造函数参数 如下所示 export class Test constructor private foo string foo private bar string bar 这给了我以下错误 参数不能有问号和初始
  • 如何强制泛型

    有没有办法在打字稿中将泛型标记为强制 function onSubmit
  • “未知”与“任何”

    TypeScript 3 0 引入unknown根据他们的 wiki 输入 未知现在是保留类型名称 因为它现在是内置类型 根据您对未知的预期用途 您可能需要删除 完全声明 有利于新引入的未知类型 或者 将其重命名为其他名称 有什么区别unk
  • Angular 没有 NameService 提供者

    我在将类加载到 Angular 组件中时遇到问题 我已经尝试解决这个问题很长时间了 我什至尝试过将所有内容合并到一个文件中 我所拥有的是 应用程序 ts
  • ionic2 对菜单项应用 ngx-translate

    我正在使用 ngx translate 来支持多语言 并且运行良好 但我也想申请菜单项 我该如何实现这一目标 我有 3 个菜单项 我想更改每个标题的语言 ts file appPages PageObj title Profile comp
  • TypeScript 中的可变参数方法至少需要一个参数

    我有一个像这样的方法 getValues args Array
  • 渲染作为 Prop 传入的 Component

    我怎样才能渲染一个StyledComponent它是作为道具传入的吗 我正在传递一个Spinner组件到我的BarComponent作为渲染的道具 我尝试使用 this props icon in my BarComponent在下面的示例
  • 在 Angular html 模板中访问常量枚举

    假设我有一个常量枚举 export const enum MyConstEnum Value1 Value1 Value2 Value2 Value3 Value3 现在我想在我的 Angular 模板中使用它 span This has
  • 从回调中访问状态

    我在从回调访问组件状态时遇到问题 国家的价值num更改正确 但此类更改对于加载时定义的回调函数不可见 import React useState from react class MyObject callback gt void cons

随机推荐