Typescript: Tvinga en typ att vara "strängliteral" och inte
Problem
Finns det ett sätt i Typescript att definiera en typ som endast är ett strängliteral, exklusive string själv?
Observera att jag inte talar om en viss lista med strängliteraler; för det skulle en enkel union av "Value1" | "Value2", eller en enum-typ fungera. Jag talar om vilken som helst strängliteral, men inte string själv.
Exempelkod
type OnlyStringLiterals = ...; // <--- what should we put here?
const v1: OnlyStringLiterals = "hi"; // should work
const v2: OnlyStringLiterals = "bye"; // should work
// and so should be for any single string value assigned
// But:
const v3: OnlyStringLiterals = ("red" as string); // should NOT work -- it's string
Användningsfall
Jag arbetar med branding på typerna i min kod, och jag skickar ett varumärkesnamn, som en mall, till min basklass. Se koden nedan:
abstract class MyAbstractClass<
BRAND_T extends string,
VALUE_T = string
> {
constructor(private readonly _value: VALUE_T) { }
getValue(): VALUE_T { return this._value; }
private _Brand?: BRAND_T; // required to error on the last line, as intended!
}
class FirstName extends MyAbstractClass<"FirstName"> {
}
class AdminRole extends MyAbstractClass<"AdminRole"> {
}
class SubClassWithMissedName extends MyAbstractClass<string> {
// I want this to error! ........................ ^^^^^^
}
function printName(name: FirstName) {
console.log(name.getValue());
}
const userFirstName = new FirstName("Alex");
const userRole = new AdminRole("Moderator");
printName(userRole); // Already errors, as expected
Lekplatslänk
Jag vill försäkra mig om att varje underklass skickar exakt ett strängliteral, och inte bara string till basklassen.
Svar #1
Jag hittade ett svar som fungerar för mitt användningsfall, men som inte är det mest återanvändbara. Delar det ändå.
Tankegång
Jag tror inte att det är möjligt att ha en fast typ för att representera det jag ville, eftersom jag inte ens kan tänka mig vad som kommer att visas i VS Code om jag hovrar över det!
Men, vad jag vet, finns det en funktionsliknande kontroll i Typescript för typer där du kan skicka in en typ och förvänta dig en typ tillbaka, och slutligen tilldela ett värde till den för att se om det går igenom.
Typkontroll med en generisk typ och en efterföljande tilldelning
Med denna teknik tänker jag på följande malltyp:
type TrueStringLiterals<T extends string> = string extends T ? never : true;
const v1 = "hi";
const check1: TrueStringLiterals<typeof v1> = true; // No error :-)
const v2 = "bye";
const check2: TrueStringLiterals<typeof v2> = true; // No error :-)
const v3 = ("red" as string);
const check3: TrueStringLiterals<typeof v3> = true; // Errors, as expected!
Lekplatslänk
Lättare i en redan passerad generisk typ
Också, i mitt användningsfall gör jag:
abstract class MyAbstractClass<
BRAND_T extends (string extends BRAND_T ? never : string),
VALUE_T = string
> {
...
Lekplatslänk
... som fungerar utmärkt!
Svar #2
Jag skulle vilja lämna in ett svar från en liknande fråga jag nyligen ställde, som är mycket enklare än de exempel som har getts hittills:
type SpecificString<S extends Exclude<string, S>> = S
let test1: SpecificString<"a" | "b" | "c"> // okay
let test2: SpecificString<string> // error
//guaranteed to work where `Exclude<string, T>` wouldn't
let test3: Exclude<SpecificString<"a" | "1">, "1">
test3 = "a" // okay
test3 = "1" // error
I princip hur detta fungerar:
Exclude<string, "any string literal"> ==> löses till string
Exclude<string, string> ==> löses till never
Du kan kalla detta F-bundet kvantifiering om du vill, antar jag.
