TypeScript:仅针对所有未定义的属性的键索引签名

jos*_*ias 11 typescript typescript-typings

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

export interface FilterItem {
  [key: string]: string | undefined;
  stringArray?: string[];
}
Run Code Online (Sandbox Code Playgroud)

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

我只是想知道是否有可能,如果可以,如何创建一个接口,该接口定义了一些属性,例如stringArray可能不遵循关键索引签名的属性。因此,键索引签名的目的只是为每个其他属性定义类型。

是否可以?

jca*_*alz 8

这是 TypeScript 中的一个悬而未决的问题;请参阅microsoft/TypeScript#17687。如果您想看到这个实现,您可能想要转到该问题并给它一个 ,但我没有任何感觉它正在积极处理。(不久前,正在对一些功能进行一些工作来实现这一点,但看起来这些工作并没有进展)。

\n\n

目前只有解决方法:

\n\n
\n\n

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

\n\n
function manipulateExistingValue(val: FilterItemIntersection) {\n    if (val.foo) {\n        console.log(val.foo.toUpperCase()); // okay        \n    }\n    val.foo = ""; // okay\n\n    if (val.stringArray) {\n        console.log(val.stringArray.map(x => x.toUpperCase()).join(",")); // okay\n    }\n    val.stringArray = [""] // okay\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是直接将值分配给该类型可能会出现错误:

\n\n
manipulateExistingValue({ stringArray: ["oops"] }); // error!     \n// -------------------> ~~~~~~~~~~~~~~~~~~~~~~~~~\n// Property \'stringArray\' is incompatible with index signature.\n
Run Code Online (Sandbox Code Playgroud)\n\n

这将迫使您跳过困难来获取该类型的值:

\n\n
const hoop1: FilterItemIntersection = \n  { stringArray: ["okay"] } as FilterItemIntersection; // assert\nconst hoop2: FilterItemIntersection = {}; \nhoop2.stringArray = ["okay"]; // multiple statements\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

另一种解决方法是将您的类型表示为通用类型而不是具体类型。将属性键表示为某种联合类型K extends PropertyKey,如下所示:

\n\n
type FilterItemGeneric<K extends PropertyKey> = \n  { [P in K]?: K extends "stringArray" ? string[] : string };\n
Run Code Online (Sandbox Code Playgroud)\n\n

获取这种类型的值需要手动注释和指定K,或者使用辅助函数来为您推断它,如下所示:

\n\n
const filterItemGeneric = \n  asFilterItemGeneric({ stringArray: ["okay"], otherVal: "" }); // okay\nasFilterItemGeneric({ stringArray: ["okay"], otherVal: ["oops"] }); // error!\n// string[] is not string ---------------->  ~~~~~~~~\nasFilterItemGeneric({ stringArray: "oops", otherVal: "okay" }); // error!\n// string\xe2\x89\xa0string[] -> ~~~~~~~~~~~\n
Run Code Online (Sandbox Code Playgroud)\n\n

这正是您想要的,但不幸的是,如果是未指定的泛型,则操作此类型的值比交集版本更困难K

\n\n
function manipulateGeneric<K extends PropertyKey>(val: FilterItemGeneric<K>) {\n    val.foo; // error! no index signature\n    if ("foo" in val) {\n        val.foo // error! can\'t narrow generic\n    }\n    val.stringArray; // error! not necessarily present\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

可以通过以下方式组合这些解决方法:在使用已知键创建和检查值时使用通用版本,而在使用未知键操作值时使用交集版本:

\n\n
const filterItem = asFilterItemGeneric({ stringArray: [""], otherVal: "" }); // okay\nfunction manipulate<K extends PropertyKey>(_val: FilterItemGeneric<K>) {\n    const val: FilterItemIntersection = _val; // succeeds\n    if (val.otherVal) {\n        console.log(val.otherVal.toUpperCase());\n    }\n    if (val.stringArray) {\n        console.log(val.stringArray.map(x => x.toUpperCase()).join(","));\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

但回过头来说,对 TypeScript 最友好的答案是首先不要使用这样的结构。如果可能,请切换到这样的方式,这样您的索引签名就不会受到不兼容值的污染:

\n\n
interface FilterItemTSFriendly {\n    stringArray?: string[],\n    otherItems?: { [k: string]: string | undefined }\n}\nconst filterItemFriendly: FilterItemTSFriendly = \n  { stringArray: [""], otherItems: { otherVal: "" } }; // okay\nfunction manipulateFriendly(val: FilterItemTSFriendly) {\n    if (val.stringArray) {\n        console.log(val.stringArray.map(x => x.toUpperCase()).join(","));\n    }\n    if (val.otherItems?.otherVal) {\n        console.log(val.otherItems.otherVal.toUpperCase());\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
\n\n

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

\n\n

游乐场链接

\n