我有以下型別:
type mapOptions = {
'a': {},
'b': {
'somethingElse': string,
'somethingDiff': number
},
'c': {
'somethingC': string
}
}
現在我想創建一個 Map,它可以將它的鍵設定為我的物件的鍵,并采用特定物件的值,如下所示:
type innerMapObject<T extends keyof mapOptions> {
status: boolean,
options: mapOptions[T]
}
基本上,我想要的是,在我擁有我的地圖之后,在其中獲取或設定值時,以獲得正確的底層選項型別:
const MyMap: Map<...> = new Map();
MyMap.set('d', {}); // error here, "d" is not a property of "mapOptions";
MyMap.set('a', {}); // works correctly
MyMap.set('c', {
"somethingD": "test"
}); // error here, the value object does not match "mapOptions["c"]"
/** This should be of type:
*
* {
* status: boolean,
* options: {
* somethingElse: string,
* somethingDiff: number
* }
* }
*
*
*/
const myBValue = MyMap.get("b");
是否可以在與該鍵關聯的值內以某種方式反向參考映射的鍵?
uj5u.com熱心網友回復:
是的,雖然代碼可能看起來有點奇怪。基本上,該Map型別有兩個泛型:K鍵型別和V值型別。這類似于Record<K, V>. 但是,由于布局的原因,無法說來自物件的某些 props 將具有某些值,因為所有鍵都具有相同的值型別。
不過,有一個解決方法,因為您可以創建Maps 的交集以使 TypeScript 推斷多載簽名以允許特定鍵具有特定值。
type MyMap = Map<"planet", { name: string; size: number }> & Map<"person", { name: string; age: number }>;
const myMap: MyMap = new Map();
// these work ??
myMap.set("planet", {
name: "Mars",
size: 21,
});
myMap.set("person", {
name: "Jon Doe",
age: 21,
});
// these fail ??
myMap.set("invalid_key", 2);
myMap.set("person", {
name: "Hey",
size: 2, // notice I'm setting `size` but it should have `age`
});
TypeScript Playground 鏈接
雖然,如果您有多個屬性,則手動撰寫此代碼可能非常少。因此,我創建了一個輔助型別,當它Map<K, V>從物件型別生成一個時,它應該更容易。它以 . 的元組的陣列型別接收鍵和值[key, value]。我使用陣列型別的原因是,如果您使用常規物件,那么鍵型別將被限制為PropertyKey( string | symbol | number),但Map允許將各種值作為鍵。
// https://stackoverflow.com/a/50375286/10873797
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type CreateMap<T extends readonly (readonly [unknown, unknown])[]> =
UnionToIntersection<
T[number] extends infer $Value // make distributive
? $Value extends readonly unknown[]
? Map<$Value[0], $Value[1]>
: never
: never
>;
type MyMap = CreateMap<[
["planet", { name: string; size: number }],
["person", { name: string; age: number }],
]>;
const myMap: MyMap = new Map();
// these work ??
myMap.set("planet", {
name: "Mars",
size: 21,
});
myMap.set("person", {
name: "Jon Doe",
age: 21,
});
// these fail ??
myMap.set("invalid_key", 2);
myMap.set("person", {
name: "Hey",
size: 2, // notice I'm setting `size` but it should have `age`
});
TypeScript Playground 鏈接
編輯:解釋
我的解決方案使用了分布式型別。在CreateMap型別中,它接受一個鍵和值的元組,如下所示:
type CreateMap<Init extends readonly (readonly [unknown, unknown])[]>
= Init[number];
// ["hey", "you"] | ["me", "too"]
type Foo = CreateMap<[
["hey", "you"],
["me", "too"]
]>;
但是,如果您嘗試簡單地將這些值插入到Map<K, V>中,那么您將失去關于什么鍵等于什么值的細節的背景關系:
type CreateMap<Init extends readonly (readonly [unknown, unknown])[]>
= Map<Init[number][0], Init[number][1]>;
// Map<"hey" | "me", "you" | "too">
type Foo = CreateMap<[
["hey", "you"],
["me", "too"]
]>;
Therefore, we would need a way to enumerate through the items in the tuple as a top-level union. Then, create a Map for each item and key pair explicitly. This diagram might be a good way to explain it:
// current way
[["hey", "you"], ["me", "too"]] => Map<"hey" | "you", "me" | "too">
// what we want
[["hey", "you"], ["me", "too"]] => Map<"hey", "me"> | Map<"me", "too">
This can be used by making our conditional types distributive on the Init[number] which will run the conditional for each item in the Init tuple type. I will do this by simply creating a type alias for the value using infer _. However, when you infer, you lose context on the types, so we must add another check to assert the inferred type is a tuple to get the key and value types:
type CreateMap<Init extends readonly (readonly [unknown, unknown])[]> =
Init[number] extends infer $Entry
? $Entry extends readonly [infer $K, infer $V]
? Map<$K, $V>
: never
: never;
// Map<"hey", "you"> | Map<"me", "too">
type Foo = CreateMap<[
["hey", "you"],
["me", "too"]
]>;
However, as you can notice, our type is a union of the maps. We need it to be an intersection so that we know the map will have all of the key/value patterns specified at once. We can do this by using @jcalz's UnionToIntersection type.
// https://stackoverflow.com/a/50375286/10873797
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type CreateMap<Init extends readonly (readonly [unknown, unknown])[]> =
UnionToIntersection<
Init[number] extends infer $Entry
? $Entry extends readonly [infer $K, infer $V]
? Map<$K, $V>
: never
: never
>;
// Map<"hey", "you"> & Map<"me", "too">
type Foo = CreateMap<[
["hey", "you"],
["me", "too"]
]>;
TypeScript Playground Link
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/454824.html
標籤:打字稿
