微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

TypeScript 模板文字类型以匹配标题案例

如何解决TypeScript 模板文字类型以匹配标题案例

我正在研究一种仅匹配标题大小写字符串的模板文字类型。我想我首先使用 Uppercase<> 创建一个单词匹配器:

const uppercaseWord: Uppercase<string> = 'U';

但它似乎也匹配小写字母:

const uppercaseWord: Uppercase<string> = 'u';

这个不会扔。

我试过参数化它:

type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;

const upperCaseWord: SingleUpperCase = 'U';

但是无法推断类型参数。它通过显式传递字符串文字来工作:

type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;

const upperCaseWord: SingleUpperCase<'u'> = 'U';

它有效,但我需要更通用的东西。匹配任何大写字符串的东西。如果我再次尝试将 string 作为类型参数传递,则错误解决,但可以正确分配小写字母:

type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;

const upperCaseWord: SingleUpperCase<string> = 'u'; // no error

我想知道这是否可能,因为它需要来自编译器的类似正则表达式的字符串匹配。

解决方法

旁白:我不确定你对“标题案例”有什么规则;我不知道 "HTML String" 是否在标题中。对于接下来的内容,我将假设您只需要确保字符串的第一个字符和每个空格 (" ") 之后的第一个字符不是小写。这意味着 "HTML String" 没问题。如果你有不同的规则,你可以调整下面答案中的代码。


TypeScript 中没有表示标题大小写的 string 的特定类型。 Template literal types 不要把这个给你; Uppercase<string>Capitalize<string> 只产生 string,因为没有更好的东西。对于真正的标题大小写 string 类型,如您所说,您需要诸如正则表达式验证的 string 类型之类的东西。 microsoft/TypeScript#41160 有一个未解决的问题,要求提供此类正则表达式验证类型的用例;如果下面的解决方案不能满足您的需求,您可能想在您的用例中评论该问题,为什么它具有吸引力,以及为什么替代解决方案不够用。


虽然这里没有特定的类型,但您可以编写一个recursive模板文字类型TitleCase<T>,它可以用作约束 T。这意味着 T extends TitleCase<T> 当且仅当 T 是标题大小写的字符串。

然后,为了避免人们不得不用某种泛型类型注释他们的字符串,你可以编写一个像 asTitleCase() 这样的辅助函数,它只返回它的输入,但如果你传入一个坏字符串。

因此,虽然您的理想解决方案如下所示:

/* THIS IS NOT POSSIBLE
const okay: TitleCase = "This Is Fine"; // okay
const error: TitleCase = "This is not fine"; // error
const alsoError: TitleCase = String(Math.random()); // error
*/

可实施解决方案如下所示:

const okay = asTitleCase("This Is Fine"); // no error
const error = asTitleCase("This is not fine"); // error!
// ---------------------> ~~~~~~~~~~~~~~~~~~
// Argument of type '"This is not fine"' is not assignable to 
// parameter of type '"This Is Not Fine"'.

const alsoError = asTitleCase(String(Math.random())); // error!
// Argument of type 'string' is not assignable to parameter of type
// '"Please Use a Title Cased String Literal Here,Thx"'

同样,这是可实施的,而不是理想的。所有使用标题大小写的字符串类型都需要获得一个额外的泛型类型参数。

请注意,除非您想在声明中直接看到错误,否则您可能不需要实际编写 asTitleCase(...)。大概你有一些关心标题大小写的函数(比如,lookupBookTitle())。如果是这样,您只需使 that 函数通用并在那里强制执行约束。因此,您只需编写 const str = asTitleCase("XXX"); lookupBookTitle(str); 而不是 const str = "XXX"; lookupBookTitle(str);,唯一的区别是错误出现的位置。

此外,在诸如 lookupBookTitle() 之类的实现中,您可能应该将输入扩大到 string 并将其视为已经过验证。即使 T extends TitleCase<T> 具有对调用者强制执行约束的效果,当 T 是未指定的泛型类型参数时,编译器将无法遵循逻辑:

// callers see a function that constrains title to TitleCase
function lookupBookTitle<T extends string>(title: VerifyTitleCase<T>): Book;

// implementer just uses string
function lookupBookTitle(title: string) {  
  const book = db.lookupByTitle(title); 
  if (!book) throw new Error("NO BOOK");
  return book;
}

无论如何,这是实现:

type TitleCase<T extends string,D extends string = " "> =
  string extends T ? never :
  T extends `${infer F}${D}${infer R}` ?
  `${Capitalize<F>}${D}${TitleCase<R,D>}` : Capitalize<T>;

类型 TitleCase<T,D> 在分隔符 T 处拆分字符串 D,并将每个部分大写(第一个字符大写)。所以它把一个字符串变成了自己的标题版本:

type X = TitleCase<"the quick brown fox jumps over the lazy dog.">
// type X = "The Quick Brown Fox Jumps Over The Lazy Dog."

然后我们可以编写一个 VerifyTitleCase<T> 类型来检查是否 T extends TitleCase<T>。如果是,则解析为 T。如果没有,它会解析为 TitleCase<T> 或一些硬编码的错误字符串,希望可以让用户知道出了什么问题。 (按照 microsoft/TypeScript#23689 中的要求,TypeScript 中没有“throw 类型”或“Invalid 类型”;因此使用硬编码的错误字符串文字是一种解决方法):

type VerifyTitleCase<T extends string> = T extends TitleCase<T> ? T :
  TitleCase<T> extends never ? "Please Use a Title Cased String Literal Here,Thx" :
  TitleCase<T>

最后,辅助函数:

const asTitleCase = <T extends string>(s: VerifyTitleCase<T>) => s;

Playground link to code

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。