TypeScrip:仅针对未定义的所有属性的密钥索引签名 [英] TypeScript: key index signature only for all properties that are not defined
本文介绍了TypeScrip:仅针对未定义的所有属性的密钥索引签名的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!
问题描述
我已经遇到过多种情况,如果能做到以下几点会更好(非常抽象):
export interface FilterItem {
[key: string]: string | undefined;
stringArray?: string[];
}
只有这样才会引发错误,因为string[]
不可分配给string
,这是有意义的。
stringArray
,这些属性可能不在键索引签名之后。因此,键索引签名仅用于定义每隔属性的类型。
可以吗?
推荐答案
这是一个未解决的问题;请参阅microsoft/TypeScript#17687。如果您想看到这一点的实现,您可能会想转到那个问题,并给它一个👍,但我没有任何感觉它正在积极地工作。(不久前,有人在一些功能上做了一些工作,这些功能本来可以实现这一点,但看起来这些功能没有取得进展。
目前只有以下解决方法:
其他答案中建议的交叉技术可能足以满足您的用例,但它们不是完全类型安全或一致的。虽然
type FilterItemIntersection = { [key: string]: string | undefined; } & { stringArray?: string[]; }
不会立即导致编译器错误,但结果类型似乎要求stringArray
属性同时为string | undefined
和string[] | undefined
,这是一个与undefined
等价的类型,而不是您想要的类型。幸运的是,您可以从FilterItemIntersection
类型的现有值中读取/写入stringArray
属性,编译器会将其视为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(","));
}
}
但支持一下,最适合打字的答案是从一开始就不要使用这样的结构。如果可能,请切换到类似以下内容,这样您的索引签名可以保持不受不兼容的值的影响:
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());
}
}
这不需要技巧、交集、泛型或泪水。如果可能的话,这就是我的建议。
好的,希望这会有帮助;祝你好运!
这篇关于TypeScrip:仅针对未定义的所有属性的密钥索引签名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文