javascript - Indexering i Typescript Record med opak typ
Följande kod ger mig ett TypeScript-fel:
type UserID = string & { readonly _: unique symbol };
interface Chat {
name: string
}
type AllChats = Record<UserID, Chat>;
const test: AllChats = {}
const userID = "ads" as UserID;
test[userID] = {
name: "my chat"
}
Jag använder UserID som en opak typ (som beskrivs här https://evertpot.com/opaque-ts-types/), så det fungerar som en sträng, men slumpmässiga strängar kan inte tilldelas en variabel av typen UserID.
Men jag får följande fel på sista raden: "Elementet har implicit typen 'any' eftersom uttrycket av typen 'UserID' inte kan användas för att indexera typen 'Record
Någon idé varför jag får detta fel? Jag förstår inte varför jag inte kan använda något av typen UserID för att indexera i en record av typen Record
UPPDATERING: Enligt denna sida https://levelup.gitconnected.com/building-type-safe-dictionaries-in-typescript-a072d750cbdf, verkar det som att jag kan uppnå det beteende jag vill genom att använda Javascript Maps istället för objekt. Men detta innebär att jag måste ge upp all objekt-syntax som Javascript erbjuder, vilket jag helst inte vill göra.
UPPDATERING2: Det verkar som att detta kan vara ett känt problem https://github.com/microsoft/TypeScript/issues/15746 som Typescript inte hanterar just nu
Svar #1
Faktiskt är detta inte möjligt. Detta beror på att objekt endast kan ha numeriska, sträng- eller symbolnycklar och inte andra, icke-primära typer.
Även om det kompilerar ner till något som skulle fungera, kan TypeScript i det här fallet inte veta detta på grund av det tidigare unique symbol-tricket.
Att använda en Map är förmodligen verkligen det enklaste sättet att göra detta. Mappar har ett ganska trevligt API som kanske inte känns irriterande.
Svar #2
Uppdatering 29 jan 2022, TypeScript 4.5.4: det fungerar.
type OPAQUE_MODIFIER = '_';
export type Opaque<T extends string> = `${OPAQUE_MODIFIER}${T}${OPAQUE_MODIFIER}${string}`
type Apple = Opaque<'apple'>;
type Orange = Opaque<'orange'>;
// Test 1
const apple: Apple = '1' as Apple;
// @ts-expect-error
const orange: Orange = apple;
// Test 2
const appleCollection: Record<Apple, string> = {
[apple]: 'value',
// @ts-expect-error <----------- The only one case that fails :(
[orange]: 'value',
};
// Test 3
appleCollection[apple] = 'newValue';
// @ts-expect-error
appleCollection[orange] = 'newValue';
Lösningen kan vara mallsträngar som index. Men denna funktion stöds inte än. Däremot är den med i nuvarande milstolpe (TS 4.3.(0,1)). Kolla detta: https://github.com/microsoft/TypeScript/issues/42192.
type OPAQUE_MODIFIER = '__OPAQUE__';
type EntityId = `${OPAQUE_MODIFIER}user${OPAQUE_MODIFIER}${string}`;
type Entity = {
title: string,
};
type EntityCollection = Record<EntityId, Entity>;
const collection: EntityCollection = {
// should works as expected (1)
['entity 1 id' as EntityId]: {
title: 'entity 1',
},
// should throws an error (2)
['entity 2 id' as string]: {
title: 'entity 2',
},
};
// should works as expected (3)
collection['entity 3 id' as EntityId] = {
title: 'entity 3',
}
// should throws an error (4)
collection['entity 4 id' as string] = {
title: 'entity 4',
}
Relaterade frågor:
- https://github.com/microsoft/TypeScript/issues/42192
- https://github.com/microsoft/TypeScript/issues/42159
- https://github.com/microsoft/TypeScript/pull/26797
