# 模板字符串类型

模板字符串类型建立在字符串字面量类型的基础上,并且能够通过联合扩展成许多字符串。

模板字符串类型的使用方式跟 javascript 中的模板字符串的时候是一样的。

(注意,变量也只能是另外一个类型)

type World = "world";

type Greeting = `hello ${World}`;

// type Greeting = "hello world";

当在插值使用联合时,该类型是每个联合成员所表示的每个可能的字符串字面值的集合:

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;

// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

对于模板字面量中的每个插值中联合相乘:

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang = "en" | "ja" | "pt";

type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;

// type LocaleMessageIDs =
//   | "en_welcome_email_id"
//   | "en_email_heading_id"
//   | "en_footer_title_id"
//   | "en_footer_sendoff_id"
//   | "ja_welcome_email_id"
//   | "ja_email_heading_id"
//   | "ja_footer_title_id"
//   | "ja_footer_sendoff_id"
//   | "pt_welcome_email_id"
//   | "pt_email_heading_id"
//   | "pt_footer_title_id"
//   | "pt_footer_sendoff_id";

# 类型中的字符串组合

模板字面值的强大之处在于基于类型中的现有字符串定义一个新字符串。

例如,JavaScript 中的一个常见模式是基于对象当前拥有的字段扩展对象。我们将为函数提供一个类型定义,它增加了对 on 函数的支持,让你知道什么时候值发生了变化:

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26,
});

person.on("firstNameChanged", (newValue) => {
  console.log(`firstName was changed to ${newValue}!`);
});

注意,在监听事件“firstNameChanged”时,而不仅仅是“firstName”,模板字面量提供了一种方法来在类型系统中处理这类字符串操作:

type PropEventSource<Type> = {
  on(
    eventName: `${string & keyof Type}Changed`,
    callback: (newValue: any) => void
  ): void;
};

/// Create a "watched object" with an 'on' method
/// so that you can watch for changes to properties.
declare function makeWatchedObject<Type>(
  obj: Type
): Type & PropEventSource<Type>;

string & keyof Type 会显式的转换为联合类型

# 使用模板文字进行推断

注意,上一个示例没有重用原始值的类型。回调使用了 any。模板文字类型可以从替换位置推断出来。

type PropEventSource<Type> = {
  on<Key extends string & keyof Type>(
    eventName: `${Key}Changed`,
    callback: (newValue: Type[Key]) => void
  ): void;
};

declare function makeWatchedObject<Type>(
  obj: Type
): Type & PropEventSource<Type>;

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26,
});

// (parameter) newName: string
person.on("firstNameChanged", (newName) => {
  console.log(`new name is ${newName.toUpperCase()}`);
});

// (parameter) newAge: number
person.on("ageChanged", (newAge) => {
  if (newAge < 0) {
    console.warn("warning! negative age");
  }
});

当用户调用字符串 “firstNameChanged” 时,TypeScript 会尝试推断 Key 的正确类型。为此,它将 Key“Changed” 之前的内容进行匹配,并推断出字符串 “firstName” 。一旦 TypeScript 确定了这一点,on 方法就可以获取原始对象上 firstName 的类型,在本例中是 string 。类似地,当调用 “ageChanged” 时,TypeScript 会找到属性 age 的类型,即 number

# 内部字符串操作类型

# Uppercase<StringType>

将字符串类型转为大写

type Greeting = "Hello, world";
type ShoutyGreeting = Uppercase<Greeting>;
// type ShoutyGreeting = "HELLO, WORLD"

# Lowercase<StringType>

将字符串类型转为小写

type Greeting = "Hello, world";
type QuietGreeting = Lowercase<Greeting>;

// type QuietGreeting = "hello, world";

# Capitalize<StringType>

将字符串类型首字母转为大写

type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;

// type Greeting = "Hello, world";

# Uncapitalize<StringType>

type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;

// type UncomfortableGreeting = "hELLO WORLD";