我已经遇到过多种情况,最好执行以下操作(非常抽象):
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 | undefined
和 string[] | 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/