typescript - 如何将 Typescript 类型定义为字符串字典但具有一个数字 "id"属性

标签 typescript

现有的 JavaScript 代码有“记录”,其中 id 是数字,其他属性是字符串。
试图定义这种类型:

type t = {
    id: number;
    [key:string]: string
}

给出错误 2411 id 类型号不能分配给字符串

最佳答案

TypeScript 中没有与您想要的结构相对应的特定类型。字符串 index signatures必须适用于每个属性,即使是手动声明的属性,如 id .您正在寻找的是“休息索引签名”或“默认属性类型”之类的东西,并且在 GitHub 中有一个公开的建议要求:microsoft/TypeScript#17867 .不久前,有一些工作可以实现这一点,但它被搁置了(有关更多信息,请参阅 this comment)。因此,尚不清楚何时或是否会发生这种情况。

您可以扩大索引签名属性的类型,以便它通过联合包含硬编码的属性,例如

type WidenedT = {
    id: number;
    [key: string]: string | number
}
但随后您必须先测试每个动态属性,然后才能将其视为 string :
function processWidenedT(t: WidenedT) {
    t.id.toFixed(); // okay
    t.random.toUpperCase(); // error
    if (typeof t.random === "string") t.random.toUpperCase(); // okay
}

在这里进行的最佳方法是,如果您可以重构您的 JavaScript,使其不会“混合”string - 值(value)袋的属性 number -值id .例如:
type RefactoredT = {
    id: number;
    props: { [k: string]: string };
}
这里idprops是完全独立的,你不需要做任何复杂的类型逻辑来确定你的属性是否是 numberstring重视。但这需要对现有 JavaScript 进行大量更改,并且可能不可行。
从现在开始,我假设您无法重构您的 JavaScript。但是请注意,与即将出现的凌乱内容相比,上面的内容是多么干净:

缺少剩余索引签名的一种常见解决方法是使用 intersection type绕过索引签名必须应用于每个属性的约束:
type IntersectionT = {
    id: number;
} & { [k: string]: string };
它有点工作;当给定类型为 IntersectionT 的值时,编译器看到 id作为 number 的属性(property)和任何其他属性(property)作为 string :
function processT(t: IntersectionT) {
    t.id.toFixed(); // okay
    t.random.toUpperCase(); // okay
    t.id = 1; // okay
    t.random = "hello"; // okay
}
但不幸的是,你不能在没有编译器提示的情况下为该类型分配一个对象文字:
t = { id: 1, random: "hello" }; // error!
// Property 'id' is incompatible with index signature.
你必须通过做类似 Object.assign() 的事情来进一步解决这个问题。 :
const propBag: { [k: string]: string } = { random: "" };
t = Object.assign({ id: 1 }, propBag);
但这很烦人,因为大多数用户永远不会想到以这种迂回的方式合成一个对象。

另一种方法是使用 generic type 代表您的类型而不是特定类型。考虑编写一个类型检查器,将候选类型作为输入,并当且仅当该候选类型与您想要的结构匹配时才返回兼容的内容:
type VerifyT<T> = { id: number } & { [K in keyof T]: K extends "id" ? unknown : string };
这将需要一个通用的辅助函数,以便您可以推断出通用的 T键入,像这样:
const asT = <T extends VerifyT<T>>(t: T) => t;
现在编译器将允许您使用对象字面量,并会按照您期望的方式检查它们:
asT({ id: 1, random: "hello" }); // okay
asT({ id: "hello" }); // error! string is not number
asT({ id: 1, random: 2 }); // error!  number is not string
asT({ id: 1, random: "", thing: "", thang: "" }); // okay
但是,使用未知键读取这种类型的值有点困难。 id属性很好,但不知道其他属性是否存在,并且您会收到错误消息:
function processT2<T extends VerifyT<T>>(t: T) {
    t.id.toFixed(); // okay
    t.random.toUpperCase(); // error! random not known to be a property
}

最后,您可以使用结合了交集和泛型类型的最佳方面的混合方法。使用泛型类型来创建值,并使用交集类型来读取它们:
function processT3<T extends VerifyT<T>>(t: T): void;
function processT3(t: IntersectionT): void {
    t.id.toFixed();
    if ("random" in t)
        t.random.toUpperCase(); // okay
}
processT3({ id: 1, random: "hello" });
以上是overloaded函数,调用者看到泛型类型,但实现看到交集类型。

Playground link to code

关于typescript - 如何将 Typescript 类型定义为字符串字典但具有一个数字 "id"属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61431397/

相关文章:

typescript - 导入 jquery.inputmask 的正确方法是什么?

typescript - typescript 和输出 javascript 文件的解决方案资源管理器 View

reactjs - 在 React-bootstrap 的 OverlayTrigger 放置属性上使用字符串变量时出现问题

javascript - 在回调函数中可观察 - Angular 5

unit-testing - 使用构造函数参数测试 Angular 2 组件

node.js - 我可以从 ts 编译器中动态排除文件吗?

angular - 如何由于另一个模型的字段而隐藏 Formly 表单的字段

Angular 5 - 使用(点击)代替 <a href ="#target"></a>

javascript - 如何从 .t​​s 文件中调用 TypeScript 函数?

javascript - 为什么在 JavaScript 中使用 TypeScript 类需要 `.default` ?