Typescript: Enforce a type to be “string literal” and not <string>

主宰稳场 提交于 2021-01-21 08:19:52

问题


Problem

Is there a way in Typescript to define a type that is only a string literal, excluding string itself?

Note that I am not talking about a certain list of string literal; for which, a simple union of "Value1" | "Value2", or an enum type would work. I am talking about any string literal, but not string itself.

Example Code

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

Use Case

I am doing Branding on the types in my code, and I am passing a brand name, as a template, to my parent class. See the code below:

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

Playground Link

I want to make sure every subclass is passing exactly a string literal, and not just string to the parent class.


回答1:


I found an answer that works for my use case, but is not the most reusable one. Just sharing it anyway.

Thought Process

I believe it's not possible to have one solid type to represent what I wanted, because I cannot even think what will show up in VS Code if I hover over it!

However, to my knowledge, there is a function-style checking in Typescript for types that you can pass a type in and expect a type back, and finally assign a value to it to see if it goes through.

Type-checking using a Generic Type and a follow-up assignment

Using this technique I am thinking about the following template type:

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!

Playground Link

Easier in an already-passed Generic Type

Also, in my use case, I am doing:

abstract class MyAbstractClass<
    BRAND_T extends (string extends BRAND_T ? never : string),
    VALUE_T = string
> {
...

Playground Link

... which works like a charm!




回答2:


You can create utility type which will allow only on subset of string:

type SubString<T> = T extends string ?
    string extends T ? never
    : T
    : never

const makeSubStr = <T extends string>(a: SubString<T>) => a
const a = makeSubStr('strLiteral')
const b = makeSubStr('strLiteral' as string) // error

const c: string = 'elo I am string'
const d = makeSubStr(c) // error

const e: SubString<"red"> = ("red" as string); // error

This type will also return never if something is not a string, in your answer TrueStringLiterals will not take this case into consideration and pass it through.



来源:https://stackoverflow.com/questions/60185084/typescript-enforce-a-type-to-be-string-literal-and-not-string

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!