打字稿:强制类型为 "string literal" 而不是 <string>
Typescript: Enforce a type to be "string literal" and not <string>
问题
在 Typescript 中有没有办法定义一个只有 string literal 的类型,不包括 string
本身?
请注意,我不是在谈论某个字符串文字列表;为此,"Value1" | "Value2"
或 enum
类型的简单联合会起作用。我说的是 任何字符串文字,而不是 string
本身。
示例代码
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
用例
我正在对我的代码中的类型进行 Branding,并且我正在将品牌名称作为模板传递给我的 parent class。请看下面的代码:
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
我想确保每个子class 都准确地传递了一个 字符串文字 ,而不仅仅是 string
到 parent class.
我找到了适用于我的用例的答案,但不是最可重用的答案。反正就是分享一下。
思考过程
我相信不可能用一种实体类型来表示我想要的东西,因为我什至无法想象如果我将鼠标悬停在它上面会在 VS Code 中显示什么!
但是,据我所知,Typescript 中有一种函数式检查类型,您可以传入一个类型并期望返回一个类型,最后为其赋值以查看它是否通过。
使用通用类型和后续分配进行类型检查
使用这种技术,我正在考虑以下模板类型:
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!
在已经通过的通用类型中更容易
此外,在我的用例中,我正在做:
abstract class MyAbstractClass<
BRAND_T extends (string extends BRAND_T ? never : string),
VALUE_T = 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
这种类型也会 return never
如果某些东西不是字符串,在你的回答中 TrueStringLiterals
不会考虑这种情况并通过它。
其他答案没有捕捉到提供的类型参数是文字字符串并集的情况。如果要明确避免这种情况,正如可以从 OP 问题中读到的那样,可以使用基于其他两个的以下解决方案:
type UnUnion<T, S> = T extends S ? ([S] extends [T] ? T : never) : never;
type NotUnion<T> = UnUnion<T, T>;
type LiteralString<T extends string> = string extends T ? never : NotUnion<T>;
其中 UnUnion
使用的事实是,如果 T
是一个联合,例如 'a' | 'b'
,则该联合分布在类型表达式的其余部分。
(['a'|'b'] extends ['a'] ? ... ) | (['a'|'b'] extends ['b'] ? ...)
如果T
是并集,其中none个可以成立,所有部分变成never
。
NotUnion
将其减少为只有一个通用参数,而 LiteralString
仅在其参数无法通过 string
扩展时使用其结果。
问题
在 Typescript 中有没有办法定义一个只有 string literal 的类型,不包括 string
本身?
请注意,我不是在谈论某个字符串文字列表;为此,"Value1" | "Value2"
或 enum
类型的简单联合会起作用。我说的是 任何字符串文字,而不是 string
本身。
示例代码
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
用例
我正在对我的代码中的类型进行 Branding,并且我正在将品牌名称作为模板传递给我的 parent class。请看下面的代码:
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
我想确保每个子class 都准确地传递了一个 字符串文字 ,而不仅仅是 string
到 parent class.
我找到了适用于我的用例的答案,但不是最可重用的答案。反正就是分享一下。
思考过程
我相信不可能用一种实体类型来表示我想要的东西,因为我什至无法想象如果我将鼠标悬停在它上面会在 VS Code 中显示什么!
但是,据我所知,Typescript 中有一种函数式检查类型,您可以传入一个类型并期望返回一个类型,最后为其赋值以查看它是否通过。
使用通用类型和后续分配进行类型检查
使用这种技术,我正在考虑以下模板类型:
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!
在已经通过的通用类型中更容易
此外,在我的用例中,我正在做:
abstract class MyAbstractClass<
BRAND_T extends (string extends BRAND_T ? never : string),
VALUE_T = 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
这种类型也会 return never
如果某些东西不是字符串,在你的回答中 TrueStringLiterals
不会考虑这种情况并通过它。
其他答案没有捕捉到提供的类型参数是文字字符串并集的情况。如果要明确避免这种情况,正如可以从 OP 问题中读到的那样,可以使用基于其他两个的以下解决方案:
type UnUnion<T, S> = T extends S ? ([S] extends [T] ? T : never) : never;
type NotUnion<T> = UnUnion<T, T>;
type LiteralString<T extends string> = string extends T ? never : NotUnion<T>;
其中 UnUnion
使用的事实是,如果 T
是一个联合,例如 'a' | 'b'
,则该联合分布在类型表达式的其余部分。
(['a'|'b'] extends ['a'] ? ... ) | (['a'|'b'] extends ['b'] ? ...)
如果T
是并集,其中none个可以成立,所有部分变成never
。
NotUnion
将其减少为只有一个通用参数,而 LiteralString
仅在其参数无法通过 string
扩展时使用其结果。