TypeScript: key 索引签名仅适用于所有未定义的属性

标签 typescript typescript-typings

我已经遇到过多种情况,最好执行以下操作(非常抽象):

export interface FilterItem {
  [key: string]: string | undefined;
  stringArray?: string[];
}

只有这样会抛出错误,因为 string[]不能分配给 string ,这是有道理的。

我只是想知道是否有可能,如果可以,如何创建一个接口(interface),该接口(interface)定义了一些属性,如 stringArray它可能不遵循 key 索引签名。因此, key 索引签名的目的只是为 定义类型。每隔属性(property)。

是否可以?

最佳答案

这是 TypeScript 中的一个未解决问题;见 microsoft/TypeScript#17687 .如果你想看到这个实现,你可能想去那个问题并给它一个 👍,但我不知道它正在积极地工作。 (不久前,我们正在对一些可以启用此功能的功能进行一些工作,但看起来这些功能并没有向前发展)。

现在只有解决方法:

其他答案中建议的交叉技术可能足以满足您的用例,但它们不是完全类型安全或一致的。而type FilterItemIntersection = { [key: string]: string | undefined; } & { stringArray?: string[]; }不会导致立即编译器错误,生成的类型似乎要求 stringArray属性都是string | undefinedstring[] | undefined , 等价于 undefined 的类型,而不是你想要的。幸运的是,您可以读/写 stringArray来自类型为 FilterItemIntersection 的现有值的属性编译器会将其视为 string[] | undefined而不是 undefined :

function manipulateExistingValue(val: FilterItemIntersection) {
    if (val.foo) {
        console.log(val.foo.toUpperCase()); // okay        
    }
    val.foo = ""; // okay

    if (val.stringArray) {
        console.log(val.stringArray.map(x => x.toUpperCase()).join(",")); // okay
    }
    val.stringArray = [""] // okay
}

但是直接将值分配给该类型可能会给您一个错误:
manipulateExistingValue({ stringArray: ["oops"] }); // error!     
// -------------------> ~~~~~~~~~~~~~~~~~~~~~~~~~
// Property 'stringArray' is incompatible with index signature.

这将迫使您跳过箍以获得该类型的值:
const hoop1: FilterItemIntersection = 
  { stringArray: ["okay"] } as FilterItemIntersection; // assert
const hoop2: FilterItemIntersection = {}; 
hoop2.stringArray = ["okay"]; // multiple statements

另一种解决方法是将您的类型表示为 generic而不是混凝土。将属性键表示为某种联合类型 K extends PropertyKey ,如:
type FilterItemGeneric<K extends PropertyKey> = 
  { [P in K]?: K extends "stringArray" ? string[] : string };

获取此类型的值涉及手动注释和指定 K ,或使用辅助函数为您推断它,如下所示:
const filterItemGeneric = 
  asFilterItemGeneric({ stringArray: ["okay"], otherVal: "" }); // okay
asFilterItemGeneric({ stringArray: ["okay"], otherVal: ["oops"] }); // error!
// string[] is not string ---------------->  ~~~~~~~~
asFilterItemGeneric({ stringArray: "oops", otherVal: "okay" }); // error!
// string≠string[] -> ~~~~~~~~~~~

这正是你想要的,但不幸的是,如果 K 则操作这种类型的值比交集版本更困难。是一个未指定的泛型:
function manipulateGeneric<K extends PropertyKey>(val: FilterItemGeneric<K>) {
    val.foo; // error! no index signature
    if ("foo" in val) {
        val.foo // error! can't narrow generic
    }
    val.stringArray; // error! not necessarily present
}

可以将这些变通方法组合在一起,在使用已知键创建和检查值时使用通用版本,但在使用未知键操作值时使用交集版本:
const filterItem = asFilterItemGeneric({ stringArray: [""], otherVal: "" }); // okay
function manipulate<K extends PropertyKey>(_val: FilterItemGeneric<K>) {
    const val: FilterItemIntersection = _val; // succeeds
    if (val.otherVal) {
        console.log(val.otherVal.toUpperCase());
    }
    if (val.stringArray) {
        console.log(val.stringArray.map(x => x.toUpperCase()).join(","));
    }
}

但是备份一下,最适合 TypeScript 的答案是一开始就不要使用这样的结构。如果可能的话,切换到这样的东西,你的索引签名可以保持不受不兼容值的污染:
interface FilterItemTSFriendly {
    stringArray?: string[],
    otherItems?: { [k: string]: string | undefined }
}
const filterItemFriendly: FilterItemTSFriendly = 
  { stringArray: [""], otherItems: { otherVal: "" } }; // okay
function manipulateFriendly(val: FilterItemTSFriendly) {
    if (val.stringArray) {
        console.log(val.stringArray.map(x => x.toUpperCase()).join(","));
    }
    if (val.otherItems?.otherVal) {
        console.log(val.otherItems.otherVal.toUpperCase());
    }
}

这不需要技巧、交叉点、泛型或眼泪。如果可能的话,这就是我的建议。

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

Playground link

关于TypeScript: key 索引签名仅适用于所有未定义的属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59896317/

相关文章:

typescript - 修改类型以包含对象的静态值而不是它们的派生类型({ test : 3 } instead of { test: number })

visual-studio-code - 如何将 VSCode 中的默认导入提示从 ES6 更改为 commonjs?

javascript - 如何使用 typescript 向对象添加属性?

node.js - 具有相同名称的 typescript 类在不同文件中重复

typescript - 创建继承@Body() 或@Param() 装饰器的自定义NestJs 装饰器?

html - 是使用 [innerHTML]、angular 4 进行数据绑定(bind)的最佳方式吗

typescript - 扩展 VSCode,自定义 UI

typescript - 如何使用TypeScript for VSCode键入JSON文件?

typescript - 使用 Cypress Typescript 类型

Typescript 本地依赖项(多个项目)